建立範例資料
CREATE TABLE emp (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(10)
);
CREATE TABLE emp_dept (
dept_id VARCHAR(4) NOT NULL,
emp_id INT NOT NULL,
emp_name VARCHAR(10),
PRIMARY KEY (dept_id, emp_id)
);
INSERT INTO emp VALUES
(1,'Dennis-1'),(2,'Dennis-2'),(3,'Dennis-3'),(4,'Dennis-4'),(5,'Dennis-5'),
(6,'Dennis-6'),(7,'Dennis-7'),(8,'Dennis-8'),(9,'Dennis-9'),(10,'Dennis-10');
INSERT INTO emp_dept VALUES
('R&D',1,'Dennis-1'),('Dev',2,'Dennis-2'),('R&D',3,'Dennis-3'),
('Test',4,'Dennis-4'),('Test',5,'Dennis-5');
五大 JOIN 類型圖解
| JOIN 類型 | Venn 圖意義 | SQL 範例 | 結果筆數 |
| INNER JOIN | 交集 | SELECT e.name, d.dept_id FROM emp e INNER JOIN emp_dept d ON e.id = d.emp_id | 5 (僅有部門的員工) |
| LEFT JOIN | 左表全保留 + 右表匹配 | SELECT e.name, d.dept_id FROM emp e LEFT JOIN emp_dept d ON e.id = d.emp_id | 10 (所有員工,無部門者 dept_id 為 NULL) |
| RIGHT JOIN | 右表全保留 + 左表匹配 | SELECT e.name, d.dept_id FROM emp e RIGHT JOIN emp_dept d ON e.id = d.emp_id | 5 (所有部門記錄,員工表無對應則 name 為 NULL) |
| CROSS JOIN | 笛卡兒積 | SELECT e.name, d.dept_id FROM emp e CROSS JOIN emp_dept d | 50 (10×5,無 ON 條件) |
| SELF JOIN | 表自我關聯 | SELECT e1.name AS emp, e2.name AS mgr FROM emp e1 LEFT JOIN emp e2 ON e1.mgr_id = e2.id | 視層級而定 |
MySQL 具體語法與優化
-- 1. 明確指定 JOIN 類型 (INNER 可省略)
SELECT e.name, d.dept_id
FROM emp e
JOIN emp_dept d ON e.id = d.emp_id; -- 等同 INNER JOIN
-- 2. 多表 JOIN 結合順序影響效能
-- 小表驅動大表:先篩選結果集較小的表
SELECT * FROM small_table s JOIN large_table l ON s.id = l.s_id;
-- 3. USING 簡化同名欄位 JOIN
SELECT * FROM emp e JOIN emp_dept d USING (emp_id); -- 需兩表欄位同名
-- 4. STRAIGHT_JOIN 強制順序 (慎用)
SELECT * FROM emp e STRAIGHT_JOIN emp_dept d ON e.id = d.emp_id;
效能優化關鍵點
- JOIN 條件欄位必須有索引:
emp.id (PK 已有)、emp_dept.emp_id (PK 一部分) 已滿足。
- 驅動表選擇: MySQL 優化器傾向選擇結果集較小的表為驅動表。可用
EXPLAIN 驗證 type 欄位是否為 ref/eq_ref。
- 避免 SELECT *: 只查詢需要欄位,減少網路傳輸、利用覆蓋索引。
- WHERE 條件下推: 將過濾條件盡早套用 (驅動表 WHERE),縮小 JOIN 運算集合。
- 衍生表/子查詢優化: MySQL 5.6+ 支援衍生表合併,舊版本需手動改寫為 JOIN。
常見陷阱