在前一篇文章中,我們一起走過了最常見的四大 SQL 基本指令:SELECT、INSERT、UPDATE、DELETE,相信你已經能在一張資料表裡做新增、刪除、修改、查詢了。但在現實的資料庫情境中,通常不可能只用到單一張表:當我們需要將多張表的資訊拼接在一起,來藉此查看更完整的資訊時,就會用到 JOIN 這個指令了。
沒錯,JOIN 可以說是 SQL 學習者在進階道路上的第一座大山。如果沒有掌握好 JOIN 的運作原理,日後你就會常常在合併資料時遇到一堆「為什麼我的查詢結果跑出空值?」「為什麼資料變重複了好幾倍?」這些奇奇怪怪的疑難雜症。
接下來在這篇文章裡,我將帶你逐一破解 INNER JOIN、LEFT JOIN、RIGHT JOIN、以及 FULL JOIN 的概念與差異,並附上程式碼示例與注意事項,讓你在面對多表查詢時,不會再手忙腳亂。
為什麼一定要學會 JOIN?
我們先來假設一個常見的真實情境:
- 你在電商網站上有一個「訂單 (orders)」表,裡面紀錄了每筆訂單的編號、日期、金額…
- 同時,你也有一個「會員 (customers)」表,裡面紀錄了每位會員的編號、姓名、email…
- 當老闆需要一份報表,顯示「每筆訂單對應到哪一位會員、那個會員的姓名和聯絡資訊」時,你就必須一次顯示來自 orders 與 customers 兩張表的資訊。
如果你只會單表查詢 (SELECT * FROM 單一表),那你就沒辦法一次顯示兩邊的內容… 而這就是 JOIN 要出馬的時候了!
理解 JOIN 的基礎概念
JOIN 的核心思路是:將兩張 (或多張) 資料表根據彼此之間「對應欄位」的值,合併成一個結果集。常見的做法是「透過欄位裡相同的值」連結起兩張表的紀錄。
舉例來說,假設我們現在有兩張表:
-- customers 表
customer_id | customer_name | email
------------+-----------------+---------------------
1001 | Alice Chen | alice@mail.com
1002 | Bob Lin | bob@mail.com
1003 | Carol Wang | carol@mail.com
-- orders 表
order_id | order_date | customer_id | amount
---------+-------------+-------------+-------
1 | 2020-03-10 | 1001 | 1200
2 | 2020-03-11 | 1002 | 600
3 | 2020-03-13 | 9999 | 900 (注意,9999 在 customers 裡並不存在)
orders.customer_id 與 customers.customer_id 這兩個欄位都存放「會員編號」,如果它們有相同的編號,就表示該筆訂單屬於該位會員,我們就可以利用這兩張表之間的共通點,把這兩個表的資訊「拼」在一起。
ON / USING / WHERE 三種寫法
在正式語法中,JOIN 的條件可用 ON 或 USING,甚至有些人會直接在 WHERE 裡面寫,但最常見且建議使用的還是 ON。對於新手來說,比較直觀的做法是:
SELECT *
FROM orders
JOIN customers
ON orders.customer_id = customers.customer_id;
這樣就會把 orders 與 customers 裡面,customer_id 欄位數值相同的資料行給「拼」在一起,成為一個查詢結果。但實際上光是 JOIN 就有很多分類,我們來一個個看。
認識四種常見 JOIN 類型
INNER JOIN
INNER JOIN 是最基本、最常用到的一種 JOIN。它的特點在於:只會得到「在兩張表都能對得上 (match)」的資料。
假設下圖是 orders 表 (左邊) 與 customers 表 (右邊),裡面都有 customer_id 欄位。當我們使用 INNER JOIN 合併時,只有那些同時在 orders 與 customers 裡面存在的 customer_id,才會出現在查詢結果裡。
SELECT orders.order_id,
orders.order_date,
customers.customer_name
FROM orders
INNER JOIN customers
ON orders.customer_id = customers.customer_id;
- orders.order_id、orders.order_date 等是 orders 表的欄位。
- customers.customer_name 等則是 customers 表的欄位。
- 只有 orders.customer_id 與 customers.customer_id 有相同值的紀錄,才會被顯示。
如果某筆訂單的 customer_id 沒在 customers 表中出現 (可能該會員已經被刪除或紀錄有誤),或是某個會員在 customers 表裡有資料但從未下過訂單,那麼這些紀錄都不會出現在 INNER JOIN 的結果裡。
什麼時候用 INNER JOIN?
- 當你只想看到「兩邊資料表都有資料」的結果時,就會使用到 INNER JOIN。舉例來說,你可能只想看到「有下過訂單的會員資料」以及「有會員資料的訂單」使用 INNER JOIN 就不會顯示沒有對應資料的紀錄(例如從沒買過東西的會員,或是找不到會員資料的訂單)。
- 絕大多數查詢 其實都是使用 INNER JOIN,因為通常我們只想要找出有成功配對的資料。
LEFT JOIN
LEFT JOIN (也叫做 Left Outer Join) 的概念是:以左表為主,所有左表的紀錄都會出現在結果中,不管它有沒有對應到右表的紀錄。若沒有對應到的話,右表的欄位就會是 NULL。
假設還是同樣的 orders (左) 與 customers (右) 表,如果我們改用 LEFT JOIN 的話:
SELECT orders.order_id,
orders.order_date,
customers.customer_name
FROM orders
LEFT JOIN customers
ON orders.customer_id = customers.customer_id;
- 左表 (left table) 是 orders。
- 在這裡使用 LEFT JOIN 就代表,即使 orders 裡有些 customer_id 根本對應不到 customers (假設該會員資料在 customers 表已被刪除),那筆訂單還是會顯示出來,只是 customer_name、customer_email 等從右表而來的欄位會顯示 NULL。
- 那這就跟我們前面提到的 INNER JOIN 就不同:INNER JOIN 會把對應不到的訂單直接濾掉,不會在結果裡。
進階:找出「沒有會員資料的訂單」
這時如果我們在 LEFT JOIN 的基礎上,加一個條件把找不到 orders.customer_id 特別挑出來的話:
WHERE orders.customer_id IS NULL;
在這裡 orders.customer_id IS NULL 的意思是:LEFT JOIN 後,右表的 customer_id 沒有值,表示這些訂單的買主跟我們的會員資料對不上。這樣就能輕鬆找出掛在幽靈會員 (不存在於會員表) 之下的訂單了。
什麼時候用 LEFT JOIN?
- 需要確保左表的所有紀錄都被列出 (即使沒有配對到右表的資料) 的時候。
- 比如你想要一個「所有訂單總覽」,不管該訂單有沒有對應到某個會員,都得列出來做後續處理。這時 LEFT JOIN 就很實用。
RIGHT JOIN
RIGHT JOIN (Right Outer Join) 跟 LEFT JOIN 的邏輯是類似的,只是角色對調:以右表為主,確保右表的紀錄全部都會在結果出現;如果左表沒有配對到紀錄的話,則會是 NULL。
SELECT orders.order_id,
customers.customer_id,
customers.customer_name
FROM orders
RIGHT JOIN customers
ON orders.customer_id = customers.customer_id;
- 假設有些 customers.customer_id 在 orders 裡根本沒有出現過,這些會員仍然會出現在結果表,只是 orders.order_id 等左表的欄位會是 NULL。
什麼時候用 RIGHT JOIN?
- 在實務上來講,比起 RIGHT JOIN,更多人會習慣用 LEFT JOIN,因為其實只要調整一下查詢順序 (把原本右表寫在左邊,左表寫在右邊) 就可以達到同樣的效果了。
- 不過,如果你覺得「以客戶 (customers) 這張表為主才是直覺」的話,也可直接使用 RIGHT JOIN。只要明白 LEFT JOIN 與 RIGHT JOIN 只是視角翻轉,就不難理解了。
FULL JOIN
FULL JOIN (Full Outer Join) 指的是:將「左右表」的所有紀錄都包含在結果中,不管它們有沒有互相配對到,只要是沒配對到的欄位就會是 NULL。
SELECT orders.order_id,
orders.order_date,
customers.customer_name
FROM orders
FULL JOIN customers
ON orders.customer_id = customers.customer_id;
- 如果某筆訂單在左表存在,但在右表找不到對應的會員,還是會顯示。
- 如果某個會員在右表存在,但從未下訂單 (左表沒有紀錄),一樣會顯示,只是 orders 相關欄位是 NULL。
什麼時候用 FULL JOIN?
- 在做資料分析或合併時,可能你想要看到「所有在 orders 表裡的訂單」 + 「所有在 customers 表裡的會員」,不想漏掉任何一邊的話,就可以使用 FULL JOIN。
- 但使用 FULL JOIN 來查詢,常常整張表會包含很多 NULL,看起來效果就會比較雜亂一點。
- 有些資料庫可能原生不支援 FULL JOIN (例如 MySQL 就沒內建 FULL OUTER JOIN,需透過 UNION 等技巧來模擬),實務中使用頻率就沒這麼高。
常見的誤區與小陷阱
JOIN 條件沒加上資料欄位關聯,會出現「乘積 (Cartesian product)」
- 如果你寫了 FROM A JOIN B,卻忘了加 ON A.x = B.x,那就會得到 A 表與 B 表之間的所有組合,查出來的數量就可能暴增。
重複欄位名,記得指定前綴 (Alias 或 TableName.欄位)
- 如果兩張表都有同樣的欄位名 (例如都叫 customer_id ),在 SELECT 時必須指定 orders.customer_id 或 customers.customer_id,否則可能會報錯或取得錯誤欄位。
- 可以使用簡短的別名 (alias) 來縮短書寫。比如把 orders 取別名為 o、customers 別名 c:
SELECT o.order_id,
o.order_date,
c.customer_name,
FROM orders AS o
INNER JOIN customers AS c
ON o.customer_id = c.customer_id;
LEFT JOIN / RIGHT JOIN 配合 WHERE 條件,可能意外導致資料被過濾
- 例如當你使用 LEFT JOIN,結果中右表的欄位如果對不到值會是 NULL,但你又在 WHERE B.some_column = 'xxx',這會把那些 NULL 的紀錄排除掉,也就失去了保留左表所有紀錄的意義。
- 需要把條件寫在 JOIN 的 ON 裡面,或使用 OR B.some_column IS NULL 等語法,避免誤刪想要保留的資料。
FULL JOIN 在 MySQL 裡無法直接用
- MySQL 沒有內建 FULL JOIN,可透過 LEFT JOIN + RIGHT JOIN 或 UNION 技巧來模擬。
- 至於 PostgreSQL、MS SQL、Oracle 等其他的資料庫,大多都可以直接使用。
小結與後續延伸
現在,你應該對 INNER JOIN、LEFT JOIN、RIGHT JOIN、以及 FULL JOIN 有了更清晰的概念,也了解何時該用哪一種 JOIN、要在 ON 裡加什麼條件、以及有哪些常見的注意事項。
如果說前一篇的「SELECT、INSERT、UPDATE、DELETE」是 SQL 的骨架,那麼 JOIN 就是讓多張表之間能互通有無的血液。若你能把 JOIN 熟練地運用在真實場景,諸如報表分析、資料整合等,就再也不怕同事、主管或老師交代給你跨多表的任務了!