楔子
到目前為止,我們的查詢都是從單個表中獲取數據。下面我們開始探討一下如何從多個表中獲取相關的數據。因為在關系數據庫中,通常將不同的信息和它們之間的聯系存儲到多個表中。比如產品表、用戶表、用戶訂單表、以及關聯的訂單明細表等。當我們想要查看某個訂單時,需要同時從這幾個表中查找關於訂單的全部信息。
作在 SQL 中,我們可以使用多表連接(JOIN)查詢獲取多個表中的關聯數據。
join 連接
連接語法:
在 SQL 的發展過程中,出現了兩種連接查詢的語法:
- ANSI SQL/86 標准,使用 FROM 和 WHERE 關鍵字指定表的連接;
- ANSI SQL/92 標准,使用 JOIN 和 ON 關鍵字指定表的連接;
當前有兩張表,一張叫girl_info、存儲了id、name、age;另一張叫girl_score、存儲了id、score。
如果我想知道某個女孩考了多少分,需要同時查詢 girl_info 和 girl_score 兩張表,這個問題就可以使用 FROM WHERE 解決。
SELECT a.name, b.score FROM girl_info AS a, girl_score AS b
WHERE a.id = b.id;
其中,FROM 子句中的逗號用於連接兩個表;同時在 WHERE 子句中指定了連接的條件是 girl_info 中的 id 等於 girl_score 表中的 id。另外,該查詢中還通過別名(a 和 b)指定了查詢的字段來自哪個表,當然不使用別名、而是使用整個表名也是可以的,只不過比較長。
當然我們這里的id字段是不重復的,如果一方的id字段重復了怎么辦?這個問題,我們先放在這里,后面再說。
對於同樣的問題,我們看看如何使用 JOIN 和 ON 實現連接查詢:
SELECT a.name, b.score FROM girl_info AS a JOIN girl_score AS b
ON a.id = b.id;
我們看到整體是差不多的,除了把兩張表改成用 JOIN 連接,WHERE 改成 ON。
JOIN 表示連接 girl_info 和 girl_score 兩張表,ON 則是用於指定連接條件,返回結果和上面是一樣的。那么我們使用哪一種呢?
推薦使用 JOIN 和 ON 進行連接查詢,它們的語義更清晰,更符合 SQL 的聲明性;另外,我們知道where還可以指定整張表的過濾條件,那么當 WHERE 中包含多個查詢條件,又用於指定表的連接關系時,會顯得比較混亂。
所以推薦使用 JOIN 和 ON,至於 WHERE,它就用來對整張表進行過濾。
SELECT a.name, b.score
FROM girl_info AS a
JOIN girl_score AS b
ON a.id = b.id
WHERE a.id > 1003;
/*
芙蘭朵露 81
霧雨魔理沙 100
坂上智代 86
*/
-- 表示只對girl_info中id大於1003的進行join
-- 當然,from where也是可以的
-- 只是我們把表的過濾、以及連接關系都寫在了where里面
SELECT a.name, b.score
FROM girl_info AS a,
girl_score AS b
WHERE a.id = b.id
AND a.id > 1003
/*
芙蘭朵露 81
霧雨魔理沙 100
坂上智代 86
*/
另外我們這里 ON 指定的是兩張表的 id 字段相等,但是不一定是 id,兩張表的其它字段也可以。並且指定的兩張表的字段也可以不一樣,比如讓一張表的 id 和另一張表的 nid 相等之類的,on后面也可以指定多個條件,使用 AND 或者 OR 連接。
連接類型:
接下來我們詳細介紹一下 SQL 中的各種連接類型。為了介紹連接類型,我們將表的數據改一下,當然結果不變,只是兩張表都增加一條數據。
1001 古明地覺 16
1002 古明地戀 15
1003 椎名真白 17
1004 芙蘭朵露 400
1005 霧雨魔理沙
1006 坂上智代 19
1007 古明地覺 16
1001 85
1002 89
1003 95
1004 81
1005 100
1006 86
1002 89
girl_info 增加一個 id 為 1001 的數據,girl_score 增加一條 id 為 1002 的數據,顯然這兩條數據是重復的。
至於SQL 支持的連接查詢,包括 內連接、外連接、交叉連接、自然連接 以及 自連接 等。其中,外連接又可以分為 左外連接、右外連接 以及 全外連接。
另外,連接查詢中的 ON 子句與 WHERE 子句類似,可以支持各種條件運算符(=、>=、!=、BETWEEN 等)。但最常用的是等值連接(=),我們主要介紹這種條件的連接查詢。
內連接:
內連接(Inner Join)返回兩個表中滿足連接條件的數據;使用關鍵字 INNER JOIN 表示,也可以簡寫成 JOIN。內連接的原理如下圖所示(基於兩個表的 id 進行等值連接):
其中,id = 1 和 id = 3 是兩個表中匹配的數據,因此內連接返回了這 2 行記錄。
左外連接:
左外連接(Left Outer Join)首先返回左表中所有的數據;對於右表,返回滿足連接條件的數據;如果沒有相應的數據就返回空值。左外連接使用關鍵字 LEFT OUTER JOIN 表示,也可以簡寫成 LEFT JOIN。左外連接的原理如下圖所示(基於兩個表的 id 進行連接):
其中,id = 2 的數據在 table1 中存在,在 table2 中不存在;左外連接仍然會返回左表中的該記錄,而對於 table2 中的價格(price),返回的是空值。
左外連接:
右外連接(Right Outer Join)首先返回右表中所有的數據;對於左表,返回滿足連接條件的數據,如果沒有相應的數據就返回空值。右外連接使用關鍵字 RIGHT OUTER JOIN 表示,也可以簡寫成 RIGHT JOIN。右外連接的原理如下圖所示(基於兩個表的 id 進行連接):
其中,id = 5 的數據在 table2 中存在,在 table1 中不存在;右外連接仍然會返回右表中的該記錄,而對於 table1 中的名稱(name),返回的是空值。簡而言之:
table1 RIGHT JOIN table2 等價於 table2 LEFT JOIN table1
因此右外連接和左外連接可以相互轉換,就我個人而言習慣左連接。如果需要右連接的邏輯,那么我會把兩張表的順序顛倒,而不會把左連接改成右連接,當然這只是我個人習慣。具體怎么做由你自己決定。
全外連接:
全外連接(Full Outer Join)等價於左外連接加上右外連接,同時返回左表和右表中所有的數據;對於兩個表中不滿足連接條件的數據返回空值。全外連接使用關鍵字 FULL OUTER JOIN 表示,也可以簡寫成 FULL JOIN 。全外連接的原理如下圖所示(基於兩個表的 id 進行連接):
MySQL不支持全外連接。
交叉連接:
交叉連接也稱為 笛卡爾積(Cartesian Product),使用關鍵字 CROSS JOIN 表示。兩個表的交叉連接相當於一個表的所有行和另一個表的所有行兩兩組合,結果的數量為兩個表的行數相乘。如果第一個表有 1000 行,第二個表有 2000 行,它們的交叉連接將會產生 2000000 行數據。
交叉連接可能會導致查詢結果的數量急劇增長,從而引起性能問題;通常應該使用連接條件進行過濾,避免產生交叉連接。
交叉連接一般使用較少。
除了上面介紹的幾種連接類型,SQL 中還存在一些特殊形式的連接查詢。
自然連接:
對於連接查詢,如果滿足以下條件,可以使用 USING 替代 ON 簡化連接條件的輸入:
連接條件是等值連接
兩個表中的連接字段必須名稱相同,類型也相同
比如我們之前的例子,根據兩張表的id字段、並且判斷是否相等,所以可以改寫如下:
select a.name, b.score
from girl_info as a
join girl_score as b
using(id)
where a.id > 1003;
得到的結果也是和之前一樣。
其中,USING 表示使用兩個表中的公共字段(id)進行等值連接。查詢語句中的公共字段不需要添加表名限定。該語句的結果與上文中的內連接查詢示例相同。
SQL Server 不支持 USING 語法。
另外一張表也可以和其自身進行連接。
自然連接:
我們目前以 id 進行連接,但是我們看到了,我們將記錄給改了,id有重復的。那么結果會怎么樣呢?
select a.id, a.name, b.score
from girl_info as a
join girl_score as b
using (id);
/*
1001 古明地覺 85
1001 古明地覺 85
1002 古明地戀 89
1003 椎名真白 95
1004 芙蘭朵露 81
1005 霧雨魔理沙 100
1006 坂上智代 86
1002 古明地戀 89
*/
我們看到 "古明地覺" 和 "古明地戀" 都出現了兩次,因為 id=1001 的記錄在 girl_info 中出現了兩次,id=1002 的記錄在 girl_score 中出現了兩次,那么在進行 join 的時候,id=1001 和 id=1002 都會出現兩次,因為能匹配上。同理,如果 girl_score 中出現了 3 個 id=1001 的記錄,那么結果 id=1001 的記錄總數就是2 * 3 = 6。因為也進行了笛卡爾積。
所以有時候在做 LEFT JOIN 之后,會發現結果數據的總數和左表不一致,明明是按照左表進行 JOIN 的呀,為啥結果和左表的總數對不上呢?出現這種情況就是 ON 后面的連接字段中出現了重復。假設按照 id 進行連接,左表有一個 id=3 的記錄,這時候右表有三個 id=3 的記錄,那么在連接的時候,左表的 id=3 會和右表的三個 id=3 的記錄進行連接,因此會變成 3 條記錄。因此如果 JOIN 之后發現結果不對,並且語法也沒有寫錯,那么數量對不上的原因十有八九就是我們目前說的數據重復的問題。
小結
連接查詢使得 SQL 能夠方便地通過一個查詢獲取多個表中的關聯數據。這節我們討論了內連接、左/右/全外連接、交叉連接、自然連接以及自連接的概念和作用。推薦使用語義更加清晰、更加通用的 JOIN 和 ON 語法實現連接查詢。