JOIN 是關聯式資料庫的核心技能,掌握各類 JOIN 的語義差異與優化技巧,能讓查詢效能提升數倍。本文以 emp/dept 經典範例,系統整理 INNER/LEFT/RIGHT/CROSS/FULL JOIN 用法,並深入剖析 ON/WHERE 執行順序對效能的影響。
| 類型 | 關鍵字 | 語義 | 備註 |
|---|---|---|---|
| 內連接 | INNER JOIN | 僅返回兩表匹配的行 | 等同於隱式內連接 FROM a,b WHERE a.id=b.id |
| 左外連接 | LEFT [OUTER] JOIN | 保留左表所有行,右表無匹配則填 NULL | MySQL 推薦用 LEFT JOIN |
| 右外連接 | RIGHT [OUTER] JOIN | 保留右表所有行,左表無匹配則填 NULL | 可用 LEFT JOIN 互換 |
| 交叉連接 | CROSS JOIN | 笛卡爾積(兩表行數相乘) | MySQL 中等同於無 ON 條件的 INNER JOIN |
| 全外連接 | FULL JOIN | 兩表所有行,無匹配填 NULL | MySQL 不支援,需用 UNION 模擬 |
-- 內連接:只顯示有部門的員工(14 筆) SELECT e.empno, e.ename, e.sal, e.deptno, d.dname FROM emp e INNER JOIN dept d ON e.deptno = d.deptno; -- 左連接:顯示所有員工,含無部門的 SCOTT(14 筆) SELECT e.empno, e.ename, e.sal, e.deptno, d.dname FROM emp e LEFT JOIN dept d ON e.deptno = d.deptno; -- 右連接:顯示所有部門,含無員工的 OPERATIONS(14 筆) SELECT e.empno, e.ename, e.sal, d.deptno, d.dname FROM emp e RIGHT JOIN dept d ON e.deptno = d.deptno; -- 全連接模擬:LEFT UNION RIGHT(6 筆) SELECT * FROM A LEFT JOIN B ON A.name = B.name UNION SELECT * FROM A RIGHT JOIN B ON A.name = B.name;
測試 10 萬筆資料,INNER JOIN ... ON 與 WHERE a.id=b.id 耗時幾乎相同,建議統一用顯式語法提升可讀性。
OUTER JOIN 會產生額外 NULL 行,增加掃描與排序成本。業務允許時優先用 INNER JOIN。
-- 推薦:條件下推到 ON,減少中間結果集 SELECT * FROM A INNER JOIN B ON B.name = A.name LEFT JOIN C ON C.name = B.name AND C.status > 1 LEFT JOIN D ON D.id = C.id AND D.status = 1; -- 不推薦:WHERE 過濾在 JOIN 完成後,中間結果集較大 SELECT * FROM A INNER JOIN B ON B.name = A.name LEFT JOIN C ON C.name = B.name LEFT JOIN D ON D.id = C.id WHERE C.status > 1 AND D.status = 1;
原理:ON 條件決定如何從右表取行;WHERE 在 JOIN 完成後再過濾。將過濾條件前置到 ON,能大幅縮減中間結果集。
-- 子查詢寫法(慢) INSERT INTO t1(a1) SELECT b1 FROM t2 WHERE NOT EXISTS (SELECT 1 FROM t1 WHERE t1.id = t2.r_id); -- JOIN 改寫(快) INSERT INTO t1(a1) SELECT b1 FROM t2 LEFT JOIN (SELECT DISTINCT id FROM t1) t1 ON t1.id = t2.r_id WHERE t1.id IS NULL;
-- ON 過濾:左表保留所有行,右表只有 id=2 的行參與匹配,其餘變 NULL SELECT * FROM product LEFT JOIN product_details ON product.id = product_details.id AND product_details.id = 2; -- 結果:4 行(product 全保留) -- WHERE 過濾:先 JOIN 再過濾,等同內連接 SELECT * FROM product LEFT JOIN product_details ON product.id = product_details.id WHERE product_details.id = 2; -- 結果:1 行(只有 id=2)