JOIN 是多表查詢核心,但 LEFT JOIN、RIGHT JOIN、INNER JOIN 在 ON 與 WHERE 條件下的語義差異極易導致業務 Bug。本文以兩表模型、實測數據對比、九組 SQL 對照,徹底講透三種連接的行為邊界與最佳實踐。
-- LEFT JOIN:左表全量,右表匹配上則顯示值,否則 NULL
SELECT L.*, R.col FROM left_table L LEFT JOIN right_table R
ON L.join_key = R.join_key AND R.filter_col = 'val'
WHERE L.filter_col = 'val';
-- RIGHT JOIN:右表全量,左表匹配上則顯示值,否則 NULL
SELECT L.*, R.col FROM left_table L RIGHT JOIN right_table R
ON L.join_key = R.join_key AND L.filter_col = 'val'
WHERE R.filter_col = 'val';
-- INNER JOIN:僅保留雙方都匹配的行(等值連接)
SELECT L.*, R.col FROM left_table L INNER JOIN right_table R
ON L.join_key = R.join_key
WHERE L.filter_col = 'val' AND R.filter_col = 'val';
-- 等價舊語法:
SELECT L.*, R.col FROM left_table L, right_table R
WHERE L.join_key = R.join_key
AND L.filter_col = 'val' AND R.filter_col = 'val';
| 連接類型 | ON 子句作用 | WHERE 子句作用 |
|---|---|---|
| LEFT JOIN | 1. 匹配右表行 2. 右表過濾條件:不滿足 → 右表列置 NULL,**不減少左表行數** | 過濾最終結果集(左表行也會被過濾掉) |
| RIGHT JOIN | 1. 匹配左表行 2. 左表過濾條件:不滿足 → 左表列置 NULL,**不減少右表行數** | 過濾最終結果集(右表行也會被過濾掉) |
| INNER JOIN | 匹配雙方行 + 雙方過濾條件:不滿足 → **直接丟棄該行**(減少結果集) | 同 ON,進一步過濾結果集 |
-- 基準:左表 ID<=6 的 6 行
SELECT ID, Username FROM left_table WHERE ID<=6; -- SQL_1
-- LEFT JOIN 無右表額外條件
SELECT L.ID, L.Username, R.CollectNum, R.BuyNum
FROM left_table L LEFT JOIN right_table R ON L.ID=R.UID
WHERE L.ID<=6; -- SQL_2:6 行,ID 2-5 右表列為 NULL
-- LEFT JOIN ON 帶右表條件 SearchNum>300
SELECT L.ID, L.Username, R.CollectNum, R.BuyNum
FROM left_table L LEFT JOIN right_table R ON L.ID=R.UID AND R.SearchNum>300
WHERE L.ID<=6; -- SQL_3:6 行,ID 6 右表列也變 NULL(條件不滿足)
-- INNER JOIN 對應 SQL_2
SELECT L.ID, L.Username, R.CollectNum, R.BuyNum
FROM left_table L INNER JOIN right_table R ON L.ID=R.UID
WHERE L.ID<=6; -- SQL_4:2 行(ID 1,6)
-- INNER JOIN 對應 SQL_3
SELECT L.ID, L.Username, R.CollectNum, R.BuyNum
FROM left_table L INNER JOIN right_table R ON L.ID=R.UID AND R.SearchNum>300
WHERE L.ID<=6; -- SQL_5:1 行(ID 1)
-- 錯誤寫法:右表條件放 WHERE → 退化為 INNER JOIN
SELECT L.ID, L.Username, R.CollectNum, R.BuyNum
FROM left_table L LEFT JOIN right_table R ON L.ID=R.UID
WHERE L.ID<=6 AND R.SearchNum>300; -- SQL_8:1 行(同 SQL_5)
-- 錯誤寫法:左表條件放 ON → 語義混亂但仍為 LEFT JOIN
SELECT L.ID, L.Username, R.CollectNum, R.BuyNum
FROM left_table L LEFT JOIN right_table R ON L.ID=R.UID AND L.ID<=6 AND R.SearchNum>300; -- SQL_9:10 行(全左表),右表條件生效,左表條件失效
ON L.key=R.key + WHERE L.status=1 AND R.type=2。WITH filtered AS (SELECT * FROM right_table WHERE SearchNum>300) SELECT ... LEFT JOIN filtered ...,語義清晰、優化器友好。JOIN 語義核心在於「誰全量保留、誰條件決定 NULL、誰條件決定丟棄」。LEFT JOIN 保左表,右表條件在 ON 控制 NULL/值,左表條件在 WHERE 控制留/捨。把這張真理表刻進肌肉記憶,配合 CTE 拆解複雜邏輯,JOIN 相關 Bug 將減少 90%以上。