LEFT JOIN中右表条件必须写在ON中才能保留左表全量;若写在WHERE中会过滤掉右表为NULL的行,等效于INNER JOIN。

LEFT JOIN里右表字段加条件,必须写在ON里才能保留左表全量
LEFT JOIN 的语义是“左表全出,右表可为空”。一旦你在 WHERE 里对右表字段做非空判断(比如 o.status = 'paid' 或 o.id IS NOT NULL),所有右表为 NULL 的行就直接被筛掉了——结果等效于 INNER JOIN。
常见错误现象:SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid' —— 没下过单的用户彻底消失,连“用户存在但无支付订单”这种合理场景都丢了。
- 正确写法:把右表业务条件挪进
ON,如ON u.id = o.user_id AND o.status = 'paid' - 这样没订单的用户仍会出现在结果中,只是
o.*全为NULL - 如果还要排除掉“有订单但状态不是 paid”的用户,那才是
WHERE的活儿;但注意:它不能碰右表字段
INNER JOIN中ON和WHERE看似等效,但语义不能混用
执行结果常一样,不代表逻辑等价。比如 JOIN orders o ON u.id = o.user_id AND o.deleted = 0 和 JOIN orders o ON u.id = o.user_id WHERE o.deleted = 0 在多数数据库里返回相同数据,但这依赖优化器重写,不是 SQL 标准保证的行为。
容易踩的坑:
- 把
o.deleted = 0写进ON,后续想改成LEFT JOIN时,语义就断了——原来过滤右表的逻辑突然变成“只让未删除订单参与连接”,而你可能本意是“查所有用户,只连未删除订单” - 某些老版本 MySQL 或 Presto 不会做等价重写,
WHERE下推时机不同,结果可能出错 -
ON里混入非关联字段(如AND o.category = 'A')会让中间结果集膨胀,索引也可能失效
ON里能写左表字段吗?可以,但作用不是“先筛左表”
比如 LEFT JOIN orders o ON u.id = o.user_id AND u.type = 'vip' 是合法的,但它不等于先 WHERE u.type = 'vip' 再连接。它的实际含义是:“只允许 VIP 用户尝试去连订单”,非 VIP 用户照样出现在结果里,只是 o.* 全为 NULL。
对比两种写法:
-
ON u.id = o.user_id AND u.type = 'vip':所有用户都出,VIP 才可能带订单数据 -
WHERE u.type = 'vip':先筛出 VIP 用户,再连订单 —— 这才是真过滤左表 - 若需“只查 VIP 用户 + 只连已支付订单”,应拆开:
ON u.id = o.user_id AND o.status = 'paid'+WHERE u.type = 'vip'
多表LEFT JOIN时,ON必须紧贴对应JOIN,顺序不能靠猜
写 FROM t1 LEFT JOIN t2 ON ... LEFT JOIN t3 ON ... 时,第二个 ON 只属于 t2 JOIN t3,它能引用 t1 和 t2 字段,但如果你误以为它只管 t3,就可能写出语义错误的条件(比如在第二个 ON 里用了 t1.x = t3.y 却没意识到 t2 已参与连接)。
实操建议:
- 每个
JOIN后立刻跟它的ON,别堆到末尾统一写 - 涉及跨三张表的关联逻辑(如
t2.a = t1.a AND t2.b = t3.b),说明表设计或查询结构已有缺陷,应考虑子查询或视图封装 - 用括号明确嵌套关系,比如
(t1 JOIN t2 ON ...) JOIN t3 ON ...,尤其在混合LEFT/INNER时
最易被忽略的点:ON 和 WHERE 的差异不在“能不能得到想要的结果”,而在“是否表达了你想表达的语义”。一旦写错位置,后续维护者、迁移数据库、扩展 JOIN 链路时,问题会层层放大。

















