多表連接是關係型資料庫的核心能力。本文系統講解 INNER JOIN、LEFT/RIGHT JOIN、CROSS JOIN 語法語義、ON vs WHERE 過濾差異、衍生表/子查詢連接、效能優化原則與常見反模式。
| 類型 | 關鍵字 | 結果集 | 口訣 |
|---|---|---|---|
| 內連接 | INNER JOIN / JOIN | 兩表滿足條件的交集 | 「只有匹配的才要」 |
| 左外連接 | LEFT [OUTER] JOIN | 左表全量 + 右表匹配行(無匹配則 NULL) | 「左表全要,右表匹配到算」 |
| 右外連接 | RIGHT [OUTER] JOIN | 右表全量 + 左表匹配行 | 「右表全要,左表匹配到算」 |
| 全外連接 | FULL [OUTER] JOIN | 兩表全量,無匹配填 NULL | MySQL 不支援,用 UNION 模擬 |
| 交叉連接 | CROSS JOIN | 笛卡爾積(無 ON 條件) | 慎用,極易產生爆炸性結果 |
-- 標準語法 SELECT a.id, a.name, b.order_no, b.amount FROM customers a LEFT JOIN orders b ON a.id = b.cust_id WHERE a.status = 1; -- WHERE 過濾最終結果集 -- ⚠️ 陷阱:LEFT JOIN 右表條件寫在 WHERE 會變成 INNER JOIN SELECT * FROM customers a LEFT JOIN orders b ON a.id = b.cust_id WHERE b.amount > 100; -- b.amount 為 NULL 的行被過濾,等同 INNER JOIN -- ✅ 正確:右表過濾條件放在 ON 子句 SELECT * FROM customers a LEFT JOIN orders b ON a.id = b.cust_id AND b.amount > 100; -- 此時無訂單或金額≤100 的客戶仍保留,b.* 為 NULL
-- 多表鏈式連接(左深樹,MySQL 單執行緒遞迴執行)
SELECT c.name, o.order_no, oi.product_name, oi.qty
FROM customers c
LEFT JOIN orders o ON c.id = o.cust_id
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE c.status = 1;
-- 衍生表(子查詢)先聚合再連接,減少連接行數
SELECT c.name, o.total_amt
FROM customers c
LEFT JOIN (
SELECT cust_id, SUM(amount) AS total_amt
FROM orders
WHERE created_at >= '2024-01-01'
GROUP BY cust_id
) o ON c.id = o.cust_id;
EXPLAIN 中 type=ref、key=idx_xxx,否則全表掃描災難。SELECT * 產生大量網路傳輸與臨時表。ON DATE(a.created) = DATE(b.created) → 無法用索引,改用範圍條件。LIMIT 100000, 20 掃描 10 萬行。SELECT * FROM a LEFT JOIN b ON a.id = b.id UNION ALL SELECT * FROM a RIGHT JOIN b ON a.id = b.id WHERE a.id IS NULL;
| 反模式 | 問題 | 重構 |
|---|---|---|
| 應用層多次查詢組裝(N+1) | 網路往返、無法利用索引連接 | 單次 JOIN 或 批次 IN 查詢 |
| 大表 JOIN 大表無索引 | Hash Join / Block Nested Loop 極慢 | 加索引、分區、衍生表預聚合 |
| LEFT JOIN 後 WHERE 右表非空條件 | 語義變 INNER JOIN,結果錯誤 | 條件移至 ON 子句 |
| SELECT * 含 BLOB/TEXT 連接 | 臨時表落盤、記憶體暴漲 | 投影所需欄位,大字段單獨查 |
JOIN 是 SQL 表達關係的語言。掌握語義邊界、優化器行為與索引配合,能寫出既正確又高效的多表查詢。