MySQL 多表查詢 – LEFT / RIGHT JOIN 完全攻略
簡介
在日常的資料庫開發與分析工作中,多表查詢是最常見也是最重要的需求之一。單純的 SELECT 從單一資料表取得資料往往不足以呈現業務的全貌,必須把相關的資料表「串接」起來才能得到完整的資訊。
JOIN 族群(INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN)就是為了這個目的而設計的,其中 LEFT JOIN 與 RIGHT JOIN 最能解決「即使另一張表沒有對應資料,也要保留主表的所有列」的情境。掌握這兩種連接方式,不僅能寫出正確的查詢,更能在效能與可讀性上取得最佳平衡。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,最後帶入實務應用場景,完整闡述 LEFT / RIGHT JOIN 的使用方式,讓你在 MySQL 開發中得心應手。
核心概念
1. 基本語法
| 連接方式 | 語法範例 | 說明 |
|---|---|---|
| LEFT JOIN(或 LEFT OUTER JOIN) | sql SELECT A.*, B.* FROM A LEFT JOIN B ON A.id = B.a_id; |
以 左表(A)為基礎,保留左表的全部列;右表(B)若沒有符合條件的資料,則以 NULL 填充右表欄位。 |
| RIGHT JOIN(或 RIGHT OUTER JOIN) | sql SELECT A.*, B.* FROM A RIGHT JOIN B ON A.id = B.a_id; |
與 LEFT JOIN 相反,以 右表(B)為基礎保留所有列,左表(A)沒有匹配時以 NULL 填充。 |
小技巧:在 MySQL 中,
LEFT JOIN與RIGHT JOIN只是一個方向的差異,實務上通常使用LEFT JOIN,因為寫法較直觀且易於維護。
2. 為什麼需要 LEFT / RIGHT JOIN
- 保留主資料:如「顧客清單」必須完整呈現,即使某些顧客尚未下單。
- 找出缺失關聯:找出「商品尚未有任何銷售紀錄」的情況。
- 報表與統計:在月度業績報表中,若有部門沒有任何業績,也要顯示為 0,而非直接被過濾掉。
3. 執行順序與效能
MySQL 在執行 JOIN 時,會先根據 ON 條件過濾右表(或左表),再根據 WHERE 條件做最終篩選。
- 先篩:
ON條件只影響連接過程,不會把左表的資料過早移除。 - 後篩:
WHERE條件會在連接完成後再套用,若在WHERE中使用右表欄位且未加上IS NULL判斷,會把本來應該保留的NULL列過濾掉,等於變成INNER JOIN。
重點:若要保留左表的所有列,不要在
WHERE子句中直接對右表欄位做等值比較,應改用ON或OR right_table.col IS NULL。
程式碼範例
以下示範 5 個常見且實用的 LEFT / RIGHT JOIN 範例,均附有說明與註解。
範例 1:顧客與訂單(左連接)
-- 目的:列出所有顧客,即使他們尚未下單
SELECT
c.customer_id,
c.name,
o.order_id,
o.order_date
FROM customers AS c
LEFT JOIN orders AS o
ON c.customer_id = o.customer_id
ORDER BY c.customer_id;
說明:customers 為左表,所有顧客都會出現在結果中;沒有訂單的顧客,其 order_id、order_date 會是 NULL。
範例 2:商品與庫存(右連接)
-- 目的:找出所有庫存記錄,即使商品資料遺失(少見情況)
SELECT
p.product_id,
p.product_name,
s.stock_qty
FROM products AS p
RIGHT JOIN stock AS s
ON p.product_id = s.product_id;
說明:此例以 stock 為右表,保留所有庫存記錄。若有庫存卻找不到對應商品(可能是資料錯誤),product_id、product_name 會顯示 NULL。
範例 3:部門業績(左連接 + 聚合)
-- 目的:列出每個部門本月的總銷售金額,部門沒有業績則顯示 0
SELECT
d.dept_id,
d.dept_name,
IFNULL(SUM(s.amount), 0) AS total_sales
FROM departments AS d
LEFT JOIN sales AS s
ON d.dept_id = s.dept_id
AND s.sale_date BETWEEN '2025-11-01' AND '2025-11-30'
GROUP BY d.dept_id, d.dept_name
ORDER BY total_sales DESC;
說明:LEFT JOIN 保留所有部門;IFNULL 把聚合結果的 NULL 轉成 0,避免顯示空白。
範例 4:找出「沒有對應資料」的紀錄(左連接 + WHERE)
-- 目的:找出尚未付款的訂單(order_status = '未付款')以及沒有付款紀錄的情況
SELECT
o.order_id,
o.customer_id,
p.payment_id
FROM orders AS o
LEFT JOIN payments AS p
ON o.order_id = p.order_id
WHERE o.status = '未付款' -- 只看未付款的訂單
AND p.payment_id IS NULL; -- 沒有付款紀錄
說明:此查詢利用 LEFT JOIN + WHERE ... IS NULL 找出「左表有、右表無」的情況,是常見的「找缺口」技巧。
範例 5:多層 LEFT JOIN(三表聯結)
-- 目的:列出每位顧客的最後一次訂單資訊以及該訂單的運送狀態
SELECT
c.customer_id,
c.name,
o.last_order_id,
o.order_date,
sh.status AS shipment_status
FROM customers AS c
LEFT JOIN (
SELECT
customer_id,
MAX(order_id) AS last_order_id,
order_date
FROM orders
GROUP BY customer_id
) AS o
ON c.customer_id = o.customer_id
LEFT JOIN shipments AS sh
ON o.last_order_id = sh.order_id;
說明:先以子查詢找出每位顧客的最新訂單(MAX(order_id)),再分兩次 LEFT JOIN 把顧客與運送狀態串起來。即使顧客沒有訂單、或訂單沒有運送紀錄,顧客資訊仍會完整保留。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
1. 在 WHERE 條件中直接比較右表欄位 |
WHERE right_table.col = value 會把 NULL 列過濾掉,等同於 INNER JOIN。 |
把條件搬到 ON 子句,或使用 OR right_table.col IS NULL。 |
2. 忘記使用 IFNULL 或 COALESCE 處理聚合結果 |
SUM、COUNT 在全部 NULL 時會回傳 NULL,導致報表顯示空白。 |
IFNULL(SUM(col),0) 或 COALESCE(SUM(col),0)。 |
3. 多表 LEFT JOIN 時產生重複列 |
右表有多筆匹配時,左表的每一列會被「Cartesian product」複製。 | 先在右表使用子查詢或 GROUP BY 彙總,或在 JOIN 前限制條件。 |
| 4. 欄位名稱衝突 | 兩表都有相同欄位名(如 id),結果集會產生歧義。 |
使用表別名(AS)或在 SELECT 中明確指定 table.column。 |
| 5. 索引缺失導致全表掃描 | JOIN 時若連接欄位未建立索引,效能會急劇下降。 |
為 ON 條件的欄位建立適當的 BTREE 索引。 |
最佳實踐
- 盡量使用左表作為主資料:左表的資料量通常較小,或是你真正想保留的集合。
- 把過濾條件放在
ON子句:能確保LEFT/RIGHT JOIN的「保留」特性不被意外破壞。 - 使用別名提升可讀性:
FROM customers AS c LEFT JOIN orders AS o,讓長查詢更易維護。 - 適時使用子查詢或 CTE(MySQL 8.0+):先把右表彙總或過濾,再進行連接,可減少重複列與提升效能。
- 檢視執行計畫:
EXPLAIN SELECT ...能幫助你確認索引是否被使用,避免不必要的全表掃描。
實際應用場景
1. 電商平台 – 顧客購買行為分析
- 需求:產出「所有顧客的最近一次購買時間」報表,即使有些顧客尚未下單。
- 解法:使用
LEFT JOIN把customers與orders(子查詢取最近一筆)連接,確保每位顧客都有一列。
2. 人事系統 – 員工出缺勤統計
- 需求:列出所有員工本月的出勤天數,未打卡的員工顯示為 0。
- 解法:
employees LEFT JOIN attendance,IFNULL(COUNT(a.date),0)產生零值。
3. 金融業務 – 客戶未完成的交易追蹤
- 需求:找出所有客戶的「未結清」交易,若客戶沒有任何交易,仍要顯示其基本資訊。
- 解法:
customers LEFT JOIN transactions ON ... AND status='未結清',再以WHERE transactions.id IS NULL OR transactions.id IS NOT NULL依需求篩選。
4. SaaS 服務 – 方案升級提醒
- 需求:列出所有使用者的當前方案,若使用者尚未訂閱任何方案,顯示「免費試用」或「未訂閱」狀態。
- 解法:
users LEFT JOIN subscriptions ON users.id = subscriptions.user_id AND subscriptions.active = 1,COALESCE(subscriptions.plan_name,'未訂閱')。
總結
LEFT JOIN與RIGHT JOIN是在多表查詢中「保留」主表全部列的利器,適用於找缺口、產生完整報表以及保證資料完整性。- 正確的 語法與條件放置(
ONvsWHERE)是避免意外變成INNER JOIN的關鍵。 - 索引、子查詢/CTE、
IFNULL/COALESCE等技巧能顯著提升效能與可讀性。 - 在實務開發中,先以 左表為主、明確別名、檢視執行計畫,並根據業務需求決定是否要使用
RIGHT JOIN(大多數情況下改寫成LEFT JOIN更易維護)。
掌握了上述概念與實作範例,你就能在 MySQL 中自如地處理各種「保留主表」的查詢需求,為系統報表、資料分析與業務流程提供堅實的資料基礎。祝開發順利,查詢不再卡卡的!