本文 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;
- 說明:此查詢先把
products與order_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,導致資料遺失。 |
先確定需求是「全部」還是「僅相符」再選擇 INNER 或 LEFT。 |
| **過度使用 SELECT *** | 直接 SELECT * 會拉回不必要欄位,增加 I/O 與記憶體負擔。 |
只挑選需要的欄位,並使用別名提升可讀性。 |
| 忽略索引 | 連接欄位若未建立索引,JOIN 執行效率會急劇下降。 | 為 ON 條件的欄位建立 索引(尤其是外鍵)。 |
| 資料量過大未分頁 | 大量 JOIN 結果直接返回前端,易造成效能瓶頸。 | 結合 LIMIT、OFFSET 或使用 分頁 技術。 |
最佳實踐小結
- 明確指定欄位:避免
SELECT *,只取必要欄位。 - 使用別名:提升可讀性,減少衝突。
- 確保索引:外鍵欄位、常用的 JOIN 條件皆應建立索引。
- 測試執行計畫:使用
EXPLAIN觀察 MySQL 如何執行 JOIN,調校索引或改寫語法。 - 適時使用子查詢或 CTE(MySQL 8.0+):讓複雜邏輯更易於維護。
實際應用場景
| 場景 | 需求 | INNER JOIN 的作用 |
|---|---|---|
| 電商平台報表 | 產生「每月各類別銷售額」報表 | 把 orders、order_items、products、categories 連接,聚合銷售金額。 |
| CRM 客戶分析 | 找出「最近 90 天內有購買行為且未回覆行銷郵件」的客戶 | customers ↔ orders ↔ email_logs 多表 JOIN,過濾條件結合日期。 |
| 庫存管理 | 列出「低於安全庫存且最近 30 天有出貨」的商品 | products ↔ stock ↔ order_items ↔ orders,使用 INNER JOIN 只保留同時符合條件的商品。 |
| 人力資源系統 | 顯示「每位員工的部門、主管與最近一次的績效評分」 | employees ↔ departments ↔ managers ↔ performance_reviews,多層 JOIN 組合。 |
| 財務對帳 | 比對「發票資料」與「收款紀錄」的對應關係 | invoices ↔ payments,只保留已對應成功的紀錄,未對應者另行處理。 |
這些情境都展示了 INNER JOIN 在資料整合、報表產出、業務決策支援中的核心價值。
總結
- INNER JOIN 是 MySQL 中最常使用的表格連接方式,僅返回同時符合條件的資料列。
- 正確使用 別名、ON/USING 條件、欄位選擇,能讓查詢既可讀又效能佳。
- 避免常見的笛卡兒積、欄位衝突與索引缺失等陷阱,並善用
EXPLAIN觀察執行計畫。 - 在實務上,INNER JOIN 幾乎是 所有多表查詢 的基礎,從電商報表、CRM 分析、庫存管理到財務對帳,都離不開它的支撐。
掌握了上述概念與技巧,你就能在日常開發中快速、正確地整合資料,為業務提供即時且可靠的資訊。祝你在 MySQL 的世界裡玩得開心、查得順利! 🚀