LEFT JOIN右表过滤条件必须写在ON子句中,否则WHERE中对右表字段的判断会使NULL行被剔除,导致左表数据丢失、语义退化为INNER JOIN;ON在连接阶段生效,保留左表全部记录,WHERE在连接后筛选整行。

LEFT JOIN 右表的过滤条件不能写在 WHERE 里——这是常见误解,也是最典型的 SQL 逻辑陷阱。正确做法是:必须写在 ON 子句中,否则会丢失左表本该保留的记录。
LEFT JOIN 后 WHERE 过滤右表字段,实际效果等于 INNER JOIN
执行顺序决定行为:ON 在连接阶段生效,WHERE 是连接完成后再筛整行。
一旦右表字段(如 o.status)出现在 WHERE 中,所有该字段为 NULL 的行(即左表无匹配右表记录)都会被直接剔除。
结果就是:左表“全量保留”的语义彻底失效。
- 错误写法:
LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid'→ 没订单或订单非 paid 的用户全消失 - 正确写法:
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'→ 用户全在,只有 paid 订单才关联上 - 本质原因:SQL 中任何与
NULL的比较(NULL = 'paid')恒为FALSE,WHERE会把这类行全拒掉
哪些条件能放 WHERE,哪些必须进 ON?
区分标准不是“左右表”,而是“你想保留什么”:
- 想保留左表全部记录?右表的筛选条件(状态、时间、类型等)一律进
ON - 想先筛左表本身?比如只查
u.age > 18的用户,这个条件可以放心放WHERE - 想排除右表为空的行?用
WHERE o.id IS NOT NULL—— 这是合法且明确的意图表达,不是误用 - 混合条件时尤其危险:比如
WHERE u.status = 'active' AND o.status = 'paid',会同时删掉u.status不满足或o.status不满足的行,左表完整性不保
多表 LEFT JOIN 时 ON 条件容易漏掉关联约束
当连续 LEFT JOIN 多张表(如 A LEFT JOIN B LEFT JOIN C),每个 ON 必须独立控制对应右表的参与逻辑:
-
B的ON只能引用A和B字段,不能依赖C -
C的ON若需关联A字段(如A.type = 'vip'),必须显式写出:ON c.user_id = b.id AND a.type = 'vip'—— 不能指望前面的WHERE已经筛过a.type - 漏写类型判断会导致交叉匹配:比如
user.type = 'vip'却没限制vip_orders的ON,普通用户也可能意外连上 VIP 订单表
不同数据库对 ON 中表达式的支持程度不一
不是所有数据库都允许在 ON 里写任意逻辑表达式:
- MySQL、PostgreSQL、SQL Server 支持
ON u.id = o.user_id AND o.created_at >= '2024-01-01' - SQLite ≤ 3.38 不支持
ON中的非等值表达式(如AND o.status = 'paid'),升级到 3.39+ 才可 - 某些 OLAP 引擎(如 Presto)对
ON中复杂条件的下推优化较弱,可能影响性能 - 跨库迁移或 ORM 自动生成 SQL 时,
ON中的业务条件容易被忽略或丢弃,比WHERE更隐蔽
真正难的不是语法,是每次写 LEFT JOIN 时,得先问自己一句:这行数据,我到底愿不愿意让它出现在最终结果里?答案决定了条件该进 ON 还是 WHERE —— 而不是凭经验或复制粘贴。

















