本文 AI 產出,尚未審核

MySQL 多表查詢 – LEFT / RIGHT JOIN 完全攻略


簡介

在日常的資料庫開發與分析工作中,多表查詢是最常見也是最重要的需求之一。單純的 SELECT 從單一資料表取得資料往往不足以呈現業務的全貌,必須把相關的資料表「串接」起來才能得到完整的資訊。

JOIN 族群(INNER JOINLEFT JOINRIGHT JOINFULL OUTER JOIN)就是為了這個目的而設計的,其中 LEFT JOINRIGHT 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 JOINRIGHT JOIN 只是一個方向的差異,實務上通常使用 LEFT JOIN,因為寫法較直觀且易於維護。

2. 為什麼需要 LEFT / RIGHT JOIN

  • 保留主資料:如「顧客清單」必須完整呈現,即使某些顧客尚未下單。
  • 找出缺失關聯:找出「商品尚未有任何銷售紀錄」的情況。
  • 報表與統計:在月度業績報表中,若有部門沒有任何業績,也要顯示為 0,而非直接被過濾掉。

3. 執行順序與效能

MySQL 在執行 JOIN 時,會先根據 ON 條件過濾右表(或左表),再根據 WHERE 條件做最終篩選。

  • 先篩ON 條件只影響連接過程,不會把左表的資料過早移除。
  • 後篩WHERE 條件會在連接完成後再套用,若在 WHERE 中使用右表欄位且未加上 IS NULL 判斷,會把本來應該保留的 NULL 列過濾掉,等於變成 INNER JOIN

重點:若要保留左表的所有列,不要在 WHERE 子句中直接對右表欄位做等值比較,應改用 ONOR 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_idorder_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_idproduct_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. 忘記使用 IFNULLCOALESCE 處理聚合結果 SUMCOUNT 在全部 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 索引。

最佳實踐

  1. 盡量使用左表作為主資料:左表的資料量通常較小,或是你真正想保留的集合。
  2. 把過濾條件放在 ON 子句:能確保 LEFT/RIGHT JOIN 的「保留」特性不被意外破壞。
  3. 使用別名提升可讀性FROM customers AS c LEFT JOIN orders AS o,讓長查詢更易維護。
  4. 適時使用子查詢或 CTE(MySQL 8.0+):先把右表彙總或過濾,再進行連接,可減少重複列與提升效能。
  5. 檢視執行計畫EXPLAIN SELECT ... 能幫助你確認索引是否被使用,避免不必要的全表掃描。

實際應用場景

1. 電商平台 – 顧客購買行為分析

  • 需求:產出「所有顧客的最近一次購買時間」報表,即使有些顧客尚未下單。
  • 解法:使用 LEFT JOINcustomersorders(子查詢取最近一筆)連接,確保每位顧客都有一列。

2. 人事系統 – 員工出缺勤統計

  • 需求:列出所有員工本月的出勤天數,未打卡的員工顯示為 0。
  • 解法employees LEFT JOIN attendanceIFNULL(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 = 1COALESCE(subscriptions.plan_name,'未訂閱')

總結

  • LEFT JOINRIGHT JOIN 是在多表查詢中「保留」主表全部列的利器,適用於找缺口、產生完整報表以及保證資料完整性。
  • 正確的 語法與條件放置ON vs WHERE)是避免意外變成 INNER JOIN 的關鍵。
  • 索引、子查詢/CTE、IFNULL/COALESCE 等技巧能顯著提升效能與可讀性。
  • 在實務開發中,先以 左表為主、明確別名、檢視執行計畫,並根據業務需求決定是否要使用 RIGHT JOIN(大多數情況下改寫成 LEFT JOIN 更易維護)。

掌握了上述概念與實作範例,你就能在 MySQL 中自如地處理各種「保留主表」的查詢需求,為系統報表、資料分析與業務流程提供堅實的資料基礎。祝開發順利,查詢不再卡卡的!