↑ 點選上面 “時代Java”關注我們, 關注新技術,學習新知識!
連接配接 (Join)
我們知道,連接配接可以了解為
篩選+笛卡爾乘積
,即便真實的算法可以做很多優化,基本思想不會變。笛卡爾乘積是固定的,讓連接配接操作變得複雜的,隻有
篩選
操作,也就是
篩選條件
。根據不同的篩選條件,SQL提供了不同的書寫方式,幫助程式員明确目的、減少意料之外的事情,也增加了學習的難度。隻要牢記,各種連接配接就是篩選條件不同,也就可以很容易地區分各種連接配接了。
以下挨個介紹常用的連接配接方式,同時呈現對應SQL的寫法。
自然連接配接 (Natural Join)
兩個表中含有相同的「屬性」,或稱「列」,在笛卡爾乘積的結果集中,篩選這些相同屬性相等的情況。
前文講解笛卡爾集合時的例子就是一種自然連接配接,相同的列是表達教師姓名的兩個屬性。但是, 從SQL的角度說,這個例子并不自動構成「自然連接配接」,因為兩個列的「名稱」不同。把
instructor
表中的列名改為
teacher
,或把
course
表中的列名改為
name
,才可以自動構成SQL意義下的「自連接配接」。
雖然SQL有所規定,但是我們知道,自然連接配接實際劃出了一類連接配接,代表「使用相等條件篩選」。隻要我們明确,「使用相等條件篩選」是有直接SQL支援的,就可以了。
說了這麼多,還沒說SQL怎麼寫。
最簡單的是直接使用SQL提供的關鍵字
natural join
。假設
student
表和
takes
表有相同的列
ID
,則以下兩行SQL等價:
select name, title from student natural join takes;select name, title from student, takes where student.ID = takes.ID;
關鍵字
natural join
相當于自動添加了
where
語句,自動檢查了兩個表的哪幾個屬性相同。
也可以同時操作多個表,多寫幾個
natural join
就可以了。當然,前提是多個表都存在同樣的列。
select name, titlefrom student natural join takes natural join course;
無論有多少個表被同時操作,SQL解釋器都會找到這些表共有的相同列,以這些列的相等為條件,篩選産生結果集。
指定連接配接條件
這樣自動找相同列的操作十分友善,同時也十分危險。要是程式員粗心地看漏了一些列名,或是對表結構有不正确的預期,可能導緻資料庫采用的相等條件比程式員設想的要多。SQL也提供了手動指定連接配接屬性的方法。
如要連接配接表示選課情況的表
takes
和課程清單
course
,指定以屬性
course_id
相等為篩選條件。以下SQL語句等價:
select name, title from takes join course using(course_id);select name, title from takes, course where takes.course_id = course.course_id;
也可以指定多個屬性,相當于多個篩選條件:
select name, title from takes join course using(course_id, course_name);
join...using...
形式的語句可以很友善地指定「相等」條件,SQL同樣提供了指定其他條件的方式,也就是
join...on...
。
on
語句同樣可以指定相等條件,如上面的SQL等價于以下SQL:
select name, title from takes join course on takes.course_id = course.course_id;
on
關鍵字可以指定任意篩選條件,這就可以實作任意形式的連接配接,也增加了我們學習的難度。
你可能認為,
where
關鍵字可以直接篩選笛卡爾乘積的結果,沒有必要使用
on
或
using
。這是個人習慣問題,見仁見智。然而,
where
通常用作對結果集最後的篩選,而
on using
專門用作連接配接時候的篩選,遵守一些規定,不但可以幫助SQL解釋器減輕負擔,還可以讓自己的工作更加清晰明了。
外連接配接 (Outer Join)
使用「自然連接配接」時,若有一個取值沒有同時出現在兩個表中,連接配接的結果集中也不會出現這個取值。若有個學生沒有選任何課程,在學生表
student
中有他,但是在選課表
takes
中沒有他,那麼以「學生姓名相等」作為連接配接篩選條件的結果集中也不會出現這個學生的名字。
在大多數情況下,我們想要這樣的效果。但是,我們有時也想保留其中一個表的資訊,不希望連接配接之後得到的表資訊缺失。這就是「外連接配接」。
當指定連接配接方式為「外連接配接」時,即便有的元素沒有同時出現在兩張表裡,也會被保留。如一個沒有選任何課的學生,指定外連接配接後,這個學生在學生表
student
中的資訊會完整出現在結果集中,而相應行中來自選課表
takes
的資訊則不會出現,以「空」顯示。可能形式如下:

外連接配接
SQL語句中對應的關鍵字是
outer join
,可以加上字首
left
或
right
或
full
,表示「左連接配接」、「右連接配接」和「全連接配接」。所謂「左右」的意思是,保留
outer join
操作符左邊或是右邊的列的資訊。如上圖的結果集,就是一個「左連接配接」的結果集,保留了左邊
student
表的資訊,而沒有保留右邊
takes
表的資訊,出現在
takes
表中的資訊要是沒有出現在
student
表中,也就不會出現在結果集中。
full
代表要同時保留兩個表的資訊,如果一個表中的資訊在另一個表中沒出現,就把沒出現的資訊顯示為「空」,道理是相同的。
上圖對應SQL語句如下,同樣使用
on
來指定連接配接條件。
select * from student left outer join takes on student.name = takes.name;
相同的結果可以由「順序相反」的右連接配接得到:
select * from takes right outer join student on student.name = takes.name;
上面兩個SQL得到的結果集相同。這提醒我們,無論是左連接配接還是右連接配接,甚至是全連接配接,重要的是指定好「要保留哪個表的資訊」,而不是「連接配接的名字是什麼」。
從内連接配接到外連接配接
「内連接配接」是和「外連接配接」相對的概念,也就是丢棄沒有同時出現在兩個表中的資料,也就是我們一開始介紹的連接配接方式。可以通過内連接配接了解外連接配接。
兩個表進行内連接配接後,有的行滿足内連接配接的篩選條件,有的不滿足。給不滿足篩選條件的,相應填補空值,得到的就是「外連接配接」的結果集。
由此,我們可以這樣了解:外連接配接=内連接配接+Null填補。
外連接配接條件使用 where
where
外連接配接的篩選條件不能用
where
寫。
考慮這樣一個SQL語句:
select * from student left outer join takes on true where student.ID = takes.ID;
這個SQL語句,連接配接的結果集是完整的、沒有篩選過的笛卡爾乘積。把連接配接的結果集通過
where
篩選,最終結果集中一定不存在
ID
不相等的行,這不是我們想要的「外連接配接」結果。
你可以停下來想一想,有沒有可能通過
where
實作外連接配接。當然是不可能的!因為「篩選」不是外連接配接的最終結果,還有填補空值這一步,并且填補空值依賴篩選的結果。
交叉連接配接
交叉連接配接就是直接保留笛卡爾乘積的結果集,不做任何處理,沒有太多好說的。普通的逗号可以觸發交叉連接配接,如
select * from student, takes;
。也可以使用
cross join
關鍵字,如
select * from student cross join takes;
。
應該不需要再多解釋了。
自連接配接
同一個表可以當做兩個表來使用,出現在
join
操作符的兩邊,相同的連接配接操作仍然适用。如列出所有的工資大小關系,顯示所有「同僚A比同僚B工資高」的情況。
select higher.name as better_employee, lower.name as lower_employeefrom salary as higher join salary as loweron higher.sal > lower.sal;
根據顯示出來的「工資比較」,你就可以去打死比你工資高的同僚了。
光是可以用來打死同僚還不太夠,「自連接配接」的操作場景更多是一種樹形結構的序列化方式,常用來處理「上下級關系」。具有上下級關系的表,通常有一個唯一表示自己的id,還有一個表示父對象的id。Linux的程序管理器就是一個很好的例子。下表是在MacOSX終端中輸入
ps -ej
得到的部分結果:
Mac程序表
PID
是「程序描述符」,
PPID
是父程序的「程序描述符」。可以看到,大多數程序的父程序都是1号程序,也就是
/sbin/launchd
,也就是Mac系統的祖宗程序。這個程序是系統啟動之後啟動的第一個程序,它的父程序的「程序描述符」是特殊值0。Mac的程序啟動次序大概如下圖,是個樹形結構。
mac boot
像Mac的程序表這樣有着樹形結構的表就是有「樹形結構」的,有唯一确定自身的id,也有「父對象」的id。當我們看到某一行,我們可以通過
PPID
找到這一行的父對象。每個對象都有一個父對象,這樣就構成了一個樹形結構,根對象的父對象用一個特殊值來表示。
可以顯示出每個程序的父程序的指令和自己的指令,SQL語句如下:
select child.command, parent.commandfrom process as child join process as parenton child.ppid = parent.pid;
連接配接總結
要寫帶有連接配接的查詢, 需要想清楚這兩件事情:
- 篩選條件是什麼?
- 是否保留隻在一個表中出現過的資訊?
其餘的,也就很簡單啦!
--
知識分享,時代前行!
~~ 時代Java
還有更多好文章……
請檢視曆史文章和官網,
↓有分享,有收獲~