本文 AI 產出,尚未審核

MySQL 教學 – JOIN 多表查詢

主題:INNER JOIN


簡介

在實務開發中,大多數資料都不是單獨存放在一張資料表裡,而是依照正規化的原則拆分成多個相關的表格。要從這些表格中抽取出完整的資訊,就必須使用 JOIN(連接)操作。
其中最常使用、也是最基礎的連接方式是 INNER JOIN,它只會返回兩張(或多張)表格中符合連接條件的資料列。了解 INNER JOIN 的運作原理與寫法,能讓你在撰寫報表、報告、或是資料分析時,快速且正確地取得所需的資料。

本篇文章將從概念、語法、實作範例一路說到常見的陷阱與最佳實踐,幫助 初學者中級開發者 能在日常開發中自信使用 INNER JOIN。


核心概念

1. INNER JOIN 的基本語法

SELECT 欄位列表
FROM 主表格 AS A
INNER JOIN 從表格 AS B
    ON A.關聯欄位 = B.關聯欄位;
  • 主表格 (A):通常是你想要「主導」結果的那張表,亦即你最先從它取出資料。
  • 從表格 (B):提供額外資訊的表格。
  • ON 子句:定義兩張表格之間的關聯條件,必須是等值比較(=)或其他可比對的運算子。

注意INNER JOIN 也可以寫成 JOIN(省略 INNER),兩者等價。


2. 多表同時 INNER JOIN

當資料需求跨越三張以上的表格時,只要把每一次的連接寫成一個 INNER JOIN,SQL 會依序處理,最終只留下同時滿足所有條件的資料列。

SELECT A.id, A.name, B.order_date, C.product_name, C.price
FROM customers AS A
INNER JOIN orders AS B       ON A.id = B.customer_id
INNER JOIN order_items AS C  ON B.id = C.order_id;

3. 欄位別名(Alias)與可讀性

在多表查詢時,別名 能大幅提升程式碼可讀性,也避免欄位名稱衝突。別名的使用方式:

SELECT 
    c.id          AS customer_id,
    c.name        AS customer_name,
    o.id          AS order_id,
    o.order_date,
    p.name        AS product_name,
    oi.quantity
FROM customers   AS c
INNER JOIN orders AS o   ON c.id = o.customer_id
INNER JOIN order_items AS oi ON o.id = oi.order_id
INNER JOIN products AS p     ON oi.product_id = p.id;

4. 使用 USING 簡化等值連接

如果兩張表的關聯欄位名稱相同,MySQL 允許使用 USING (欄位),省去 ON 子句的完整寫法:

SELECT *
FROM employees AS e
INNER JOIN departments AS d USING (dept_id);

提醒USING 會自動去除重複的欄位,只保留一個欄位在結果中。


5. 結合聚合函式(GROUP BY)與 INNER JOIN

在實務上,我們常會先 JOIN聚合(如計算總金額、筆數)。以下範例示範「每位客戶的總消費金額」:

SELECT 
    c.id          AS customer_id,
    c.name        AS customer_name,
    SUM(oi.quantity * p.price) AS total_spent
FROM customers AS c
INNER JOIN orders AS o       ON c.id = o.customer_id
INNER JOIN order_items AS oi ON o.id = oi.order_id
INNER JOIN products AS p      ON oi.product_id = p.id
GROUP BY c.id, c.name
ORDER BY total_spent DESC;

程式碼範例

以下提供 5 個實務上常見 的 INNER JOIN 範例,並附上說明與註解。

範例 1:取得每筆訂單的客戶資訊

/* 查詢 orders 表與 customers 表的關聯資料 */
SELECT 
    o.id          AS order_id,
    o.order_date,
    c.id          AS customer_id,
    c.name        AS customer_name,
    c.email
FROM orders AS o
INNER JOIN customers AS c
    ON o.customer_id = c.id
ORDER BY o.order_date DESC;
  • 說明:只會回傳那些 已有客戶資料 的訂單;若有孤兒訂單(customer_id 為 NULL 或不存在)則不會出現在結果中。

範例 2:列出每位員工所屬的部門名稱

/* 使用別名與 USING 簡化語法 */
SELECT 
    e.id          AS employee_id,
    e.name        AS employee_name,
    d.name        AS department_name
FROM employees AS e
INNER JOIN departments AS d USING (dept_id);
  • 說明USING (dept_id) 省去 ON e.dept_id = d.dept_id,結果只保留一個 dept_id 欄位。

範例 3:找出同時購買「筆記型電腦」與「滑鼠」的客戶

/* 兩次 INNER JOIN 同一張表,利用子查詢過濾 */
SELECT DISTINCT c.id, c.name, c.email
FROM customers AS c
INNER JOIN orders AS o1      ON c.id = o1.customer_id
INNER JOIN order_items AS i1 ON o1.id = i1.order_id
INNER JOIN products AS p1    ON i1.product_id = p1.id
WHERE p1.name = '筆記型電腦'
AND EXISTS (
    SELECT 1
    FROM orders AS o2
    INNER JOIN order_items AS i2 ON o2.id = i2.order_id
    INNER JOIN products AS p2    ON i2.product_id = p2.id
    WHERE o2.customer_id = c.id
      AND p2.name = '滑鼠'
);
  • 說明:使用 EXISTS 搭配內部的 INNER JOIN,只保留同時購買兩項商品的客戶。

範例 4:計算每個產品的總銷售量與營收

SELECT 
    p.id          AS product_id,
    p.name        AS product_name,
    SUM(oi.quantity)                AS total_quantity,
    SUM(oi.quantity * p.price)       AS total_revenue
FROM products AS p
INNER JOIN order_items AS oi ON p.id = oi.product_id
GROUP BY p.id, p.name
ORDER BY total_quantity DESC;
  • 說明:此查詢先把 productsorder_items 連接,再以 GROUP BY 聚合,每個產品只會出現一次。

範例 5:取得最近 30 天內,未付款的訂單與客戶資料

SELECT 
    o.id          AS order_id,
    o.order_date,
    c.id          AS customer_id,
    c.name        AS customer_name,
    o.total_amount,
    o.payment_status
FROM orders AS o
INNER JOIN customers AS c
    ON o.customer_id = c.id
WHERE o.payment_status = '未付款'
  AND o.order_date >= CURDATE() - INTERVAL 30 DAY
ORDER BY o.order_date DESC;
  • 說明:結合 日期運算條件過濾,快速找出需要跟進的未付款訂單。

常見陷阱與最佳實踐

陷阱 說明 解決方案
忘記加上 ON 條件 INNER JOIN 若省略 ON,會變成 Cartesian product(笛卡兒積),資料量爆炸。 必須嚴格指定 ON(或 USING)條件。
欄位名稱衝突 多表同名欄位(如 id)會造成結果不明確或錯誤。 使用 別名AS)或在 SELECT 中明確寫 table.column
使用 LEFT JOIN 卻寫成 INNER JOIN 想保留左表全部資料卻誤用 INNER JOIN,導致資料遺失。 先確定需求是「全部」還是「僅相符」再選擇 INNERLEFT
**過度使用 SELECT *** 直接 SELECT * 會拉回不必要欄位,增加 I/O 與記憶體負擔。 只挑選需要的欄位,並使用別名提升可讀性。
忽略索引 連接欄位若未建立索引,JOIN 執行效率會急劇下降。 ON 條件的欄位建立 索引(尤其是外鍵)。
資料量過大未分頁 大量 JOIN 結果直接返回前端,易造成效能瓶頸。 結合 LIMITOFFSET 或使用 分頁 技術。

最佳實踐小結

  1. 明確指定欄位:避免 SELECT *,只取必要欄位。
  2. 使用別名:提升可讀性,減少衝突。
  3. 確保索引:外鍵欄位、常用的 JOIN 條件皆應建立索引。
  4. 測試執行計畫:使用 EXPLAIN 觀察 MySQL 如何執行 JOIN,調校索引或改寫語法。
  5. 適時使用子查詢或 CTE(MySQL 8.0+):讓複雜邏輯更易於維護。

實際應用場景

場景 需求 INNER JOIN 的作用
電商平台報表 產生「每月各類別銷售額」報表 ordersorder_itemsproductscategories 連接,聚合銷售金額。
CRM 客戶分析 找出「最近 90 天內有購買行為且未回覆行銷郵件」的客戶 customersordersemail_logs 多表 JOIN,過濾條件結合日期。
庫存管理 列出「低於安全庫存且最近 30 天有出貨」的商品 productsstockorder_itemsorders,使用 INNER JOIN 只保留同時符合條件的商品。
人力資源系統 顯示「每位員工的部門、主管與最近一次的績效評分」 employeesdepartmentsmanagersperformance_reviews,多層 JOIN 組合。
財務對帳 比對「發票資料」與「收款紀錄」的對應關係 invoicespayments,只保留已對應成功的紀錄,未對應者另行處理。

這些情境都展示了 INNER JOIN 在資料整合、報表產出、業務決策支援中的核心價值。


總結

  • INNER JOIN 是 MySQL 中最常使用的表格連接方式,僅返回同時符合條件的資料列。
  • 正確使用 別名、ON/USING 條件、欄位選擇,能讓查詢既可讀效能佳
  • 避免常見的笛卡兒積、欄位衝突與索引缺失等陷阱,並善用 EXPLAIN 觀察執行計畫。
  • 在實務上,INNER JOIN 幾乎是 所有多表查詢 的基礎,從電商報表、CRM 分析、庫存管理到財務對帳,都離不開它的支撐。

掌握了上述概念與技巧,你就能在日常開發中快速、正確地整合資料,為業務提供即時且可靠的資訊。祝你在 MySQL 的世界裡玩得開心、查得順利! 🚀