上一篇我們學了 JOIN,可以把兩張表合併在一起。
但 JOIN 有一個盲點,在某些情況下會讓你的資料無聲無息地消失。
這篇來看這個問題是什麼,以及怎麼解決。
JOIN 的盲點:沒有對應的資料會消失
回顧一下我們的會員表和訂單表:
會員表:
訂單表:
注意:Tom(會員ID = 3)沒有下過任何訂單,所以訂單表裡面沒有他的資料。
當我們用 JOIN 合併這兩張表:
SELECT *
FROM 會員 JOIN 訂單 ON 會員.會員ID = 訂單.會員ID合併結果:
Tom 消失了。
為什麼?
回想上一篇學過的:JOIN 底層做的事情,就是先排列組合,再用合併條件篩選。
把所有組合列出來:
合併條件是 會員.會員ID = 訂單.會員ID。
John 和 Mary 都有滿足條件的組合,所以出現在合併結果裡。
Tom 的會員ID 是 3,但訂單表裡面沒有任何一筆訂單的會員ID 是 3,所以九個組合裡面,Tom 那三列全部都不滿足條件。
結果就是:Tom 整個人從結果裡消失了。
JOIN 資料消失會造成什麼問題?
你可能會想:「Tom 沒下單,消失就消失啊,有差嗎?」
來看一個情境:
老闆說:「我想看所有會員的客單價分佈,沒有下單的會員,客單價就算 0 元。」
也就是說,老闆期望看到的結果長這樣:
但我們用 JOIN 合併之後,Tom 整個人消失了,合併結果長這樣:
Tom 不在這張表裡面。
你可能會想:「那我用逢空補值,遇到空的就補 0 不就好了?」
問題是,逢空補值的前提是「資料要在,只是值是空的」。
但現在 Tom 這筆資料根本不存在,整列都消失了,連補值的機會都沒有。
內部合併(INNER JOIN)
我們前面學的 JOIN,其實完整的名字叫做 INNER JOIN(內部合併)。
為什麼叫「內部」?
因為 INNER 這個字有「向內縮」的意思,可以想像一個漏斗:兩張表的資料從上面倒進去,但漏斗的出口很窄,只有在兩張表都找得到的資料才能通過,其餘的全部被擋在外面丟掉。
所以叫「內部」,就是因為它把結果縮到了最核心的部分:只留下兩邊都有的,其他全部排除。
也就是說:
- 會員表的資料,要在訂單表找到對應,才會出現
- 訂單表的資料,要在會員表找到對應,才會出現
任何一邊找不到對應,那筆資料就消失。
像 Tom 在訂單表找不到對應,所以他就消失了。
你可以把它想成「取交集」:只取兩邊都有的部分。
平常我們寫的:
表單A JOIN 表單B ON 合併條件其實就是:
表單A INNER JOIN 表單B ON 合併條件INNER 可以省略不寫,效果一樣。
外部合併(OUTER JOIN):保留沒有對應的資料
既然內部合併會把沒有對應的資料排除掉,那我們需要一種方法,把這些資料保留下來。
這種方法叫做 外部合併(OUTER JOIN)。
OUTER 的意思是「外部的」,跟 INNER(內部的)相反。
INNER JOIN 把結果縮到最核心的部分,只留下兩邊都有的資料。那些只存在於某一邊、在另一邊找不到對應的資料,就被排除在核心之外。
OUTER JOIN 則是把範圍往外擴展,把這些在另一邊找不到對應、被 INNER JOIN 排除掉的資料也保留下來。
但保留哪一邊?
回想一下前面的語法:表單A JOIN 表單B ON 合併條件。
合併的時候有兩張表,要保留表單 A?表單 B?還是兩邊都保留?
這裡的「哪一邊」,就是看你書寫的順序:寫在 JOIN 前面的叫左邊,寫在 JOIN 後面的叫右邊。
外部合併根據要保留哪一邊,分成三種:
LEFT JOIN 和 RIGHT JOIN 怎麼選?
以我們的例子來說,Tom 是會員,但他沒有下過任何訂單,所以用 INNER JOIN 合併的時候,他在訂單表找不到對應,整個人就消失了。
如果我們想保留所有會員的資料,包含 Tom 這種沒有下單的會員,就不能用 INNER JOIN,要改用 OUTER JOIN。
那要用哪一種?取決於你把會員表寫在哪一邊。
會員寫在左邊:用 LEFT JOIN
SELECT *
FROM 會員 LEFT JOIN 訂單 ON 會員.會員ID = 訂單.會員ID會員在 JOIN 的左邊,用 LEFT JOIN 就能保留所有會員。
會員寫在右邊:用 RIGHT JOIN
SELECT *
FROM 訂單 RIGHT JOIN 會員 ON 訂單.會員ID = 會員.會員ID會員在 JOIN 的右邊,用 RIGHT JOIN 一樣能保留所有會員。
兩種寫法,結果是一樣的
-- 這兩個寫法,結果是一樣的
會員 LEFT JOIN 訂單 ON ...
訂單 RIGHT JOIN 會員 ON ...兩種寫法的合併結果都是:
Tom 復活了。
不管你用哪種寫法,結果都一樣:會員表的每一筆資料都被保留下來,就算在訂單表找不到對應也不會消失。
Tom 在訂單表找不到對應,所以訂單那邊的欄位自動填 NULL(空值)。
這樣就可以做逢空補值了:遇到金額是 NULL 的時候,補 0 就好。
只是把表的順序調換一下而已。
實務上怎麼寫?一路 LEFT JOIN
既然 LEFT JOIN 和 RIGHT JOIN 結果一樣,那為什麼要有兩種寫法?
因為彈性。
在實務上,大部分的人習慣把主要關注的表放在左邊,然後一路 LEFT JOIN 下去。
什麼意思?假設你現在有三張表:
會員表(主要關注的表):
訂單表(輔助表):
客服記錄表(輔助表):
你會這樣寫:
FROM 會員
LEFT JOIN 訂單 ON 會員.會員ID = 訂單.會員ID
LEFT JOIN 客服記錄 ON 會員.會員ID = 客服記錄.會員ID第一步:LEFT JOIN 訂單
LEFT JOIN 訂單 ON 會員.會員ID = 訂單.會員ID合併結果:
Tom 沒有訂單,但因為是 LEFT JOIN,他被保留下來,訂單欄位填 NULL。
第二步:再 LEFT JOIN 客服記錄
LEFT JOIN 客服記錄 ON 會員.會員ID = 客服記錄.會員ID在上一步的結果上,再合併客服記錄:
Mary 沒有客服記錄,客服欄位填 NULL。
但三個會員都還在,沒有任何一個人消失。
不管合併幾張表,會員表的資料永遠不會消失,因為它在最左邊。
這就是「一路 LEFT JOIN」的意思:主要的表放在最左邊,後面的輔助表一張一張往右接上去。
FULL JOIN:兩邊都保留
FULL JOIN 就是:不管左邊還是右邊,全部都保留。
SELECT *
FROM 會員 FULL JOIN 訂單 ON 會員.會員ID = 訂單.會員ID左邊找不到對應?保留,右邊的欄位填 NULL。
右邊找不到對應?保留,左邊的欄位填 NULL。
在我們的例子裡,FULL JOIN 和 LEFT JOIN 的結果是一樣的:
為什麼結果一樣?回想一下前幾篇學過的 Foreign Key:訂單表的會員ID 參照了會員表的會員ID,所以每一筆訂單的會員ID,在會員表裡一定找得到對應。
不可能出現「有訂單但找不到會員」的情況。
所以 FULL JOIN 多保留的「右邊找不到對應」的情況,在這裡根本不會發生,結果自然跟 LEFT JOIN 一樣。
FULL JOIN 在實務上比較少用,但知道有這個選項就好。
練習:找出沒有養狗的人
來做一個練習。
假設我們有兩張表:
人類表:
狗狗表:
一個人可以養很多隻狗,但一隻狗只能登記一個飼主。
人類可能不養狗,狗狗也可能沒有人養(流浪狗)。
問題:找出還沒有養狗的人
想一下:我們要保留哪一邊?
我們要找的是「沒有養狗的人」,也就是:有這個人,但他在狗狗表找不到任何對應。
所以人類的資料我全都要保留,不管他有沒有養狗。
至於沒有人養的狗?我們現在不在意,因為目標是找「沒有養狗的人」,不是找「沒人養的狗」。
把人類放在左邊,用 LEFT JOIN:
SELECT *
FROM 人類 LEFT JOIN 狗狗 ON 人類.人類ID = 狗狗.飼主ID合併結果:
小華沒有養狗,所以狗狗那邊的欄位全部是 NULL。
接下來只要篩選「狗狗ID 是 NULL」的資料,就能找出沒有養狗的人:
SELECT 人類.人類ID, 姓名
FROM 人類 LEFT JOIN 狗狗 ON 人類.人類ID = 狗狗.飼主ID
WHERE 狗狗.狗狗ID IS NULL結果:
這也是一個很常見的做法:用 LEFT JOIN 配合 WHERE ... IS NULL,找出「在另一張表沒有對應」的資料。
不需要寫 OUTER
你可能注意到一件事:我們一直在講「外部合併」,但寫的時候都沒有寫 OUTER。
-- 不需要寫 OUTER
LEFT JOIN -- 不用寫 LEFT OUTER JOIN
RIGHT JOIN -- 不用寫 RIGHT OUTER JOIN
FULL JOIN -- 不用寫 FULL OUTER JOIN因為你寫了 LEFT、RIGHT 或 FULL,資料庫就知道你在做外部合併了,不需要再多寫 OUTER。
就像 INNER JOIN 可以省略 INNER,直接寫 JOIN 一樣。
四種 JOIN 總整理
其中最常用的是 JOIN 和 LEFT JOIN。
不寫任何東西(純 JOIN)就是內部合併,這是預設值,也是最嚴格的合併方式:只要任何一邊沒有對應,就排除。
為什麼預設是最嚴格的?因為資料庫的設計哲學是「沒有特別指定,就做最小量的合併」,比較安全。
小結
這篇的重點只有一個:JOIN 會把沒有對應的資料排除掉。
如果你需要保留這些資料,就用 LEFT JOIN。
什麼時候用 JOIN,什麼時候用 LEFT JOIN?
問自己一個問題:「如果某筆資料在另一張表找不到對應,我還需要它嗎?」
- 不需要 → 用 JOIN(內部合併)
- 需要 → 用 LEFT JOIN(以左合併)
大部分的情境下,你會把主要關注的表放在左邊,然後一路 LEFT JOIN 下去。
主要的表「我全都要」,輔助的表「有就拿,沒有就填 NULL」。
這是實務上最常見的寫法。