LEFT JOIN中右表条件写在WHERE会丢左表数据,因WHERE在连接后执行,将右表为NULL的行整行剔除,使LEFT JOIN退化为INNER JOIN;正确做法是将右表条件移至ON子句。

LEFT JOIN里右表条件写在WHERE会丢左表数据
这是最常踩的坑:本想查“所有用户及其已支付订单”,结果只返回了有支付订单的用户。问题就出在把 o.status = 'paid' 放到了 WHERE 里。
LEFT JOIN 的语义是“左表全保留”,但 WHERE 是连接完成后再过滤整行。只要右表字段为 NULL(比如没订单),o.status = 'paid' 就是 UNKNOWN,整行被剔除——LEFT JOIN 实质退化成 INNER JOIN。
-
ON u.id = o.user_id AND o.status = 'paid':只让已支付的订单参与匹配,没订单的用户仍保留,o.*全为NULL -
ON u.id = o.user_id WHERE o.status = 'paid':先全连,再筛,没订单的用户直接消失 - 哪怕加了
IS NOT NULL判断,比如WHERE o.order_id IS NOT NULL,效果也一样——它筛的是最终结果,不是匹配逻辑
INNER JOIN中ON和WHERE结果相同但执行路径不同
表面上看,INNER JOIN ... ON a.id = b.id WHERE b.deleted = 0 和 ON a.id = b.id AND b.deleted = 0 返回一样的行。但这只是优化器“帮忙重写”的结果,不是标准保证。
真正区别在执行阶段:
-
ON中的条件参与连接算法决策:数据库可能用b.deleted走索引、提前剪枝右表,减少中间计算量 -
WHERE中的条件是连接完才扫一遍结果集,多一次 filter 操作,对大表可能明显拖慢 - 某些旧版 MySQL 或 Presto 不会自动下推
WHERE条件到连接阶段,导致结果或性能偏差
ON里能写非关联字段,但要清楚它实际做什么
ON 子句允许写左表或右表的任意字段,比如 ON u.id = o.user_id AND u.type = 'vip'。这不是“先筛左表”,而是“只让 VIP 用户尝试去连右表”。
非 VIP 用户依然出现在结果里,只是右表字段全为 NULL。如果真想只查 VIP 用户,得用 WHERE u.type = 'vip'。
- 想保留所有用户 + 只连活跃订单 →
ON u.id = o.user_id AND o.status = 'active' - 想只查 VIP 用户 + 连任意订单 →
ON u.id = o.user_id WHERE u.type = 'vip' - 想只查 VIP 用户 + 只连活跃订单 → 两个条件分开放:
ON u.id = o.user_id AND o.status = 'active'+WHERE u.type = 'vip'
多表LEFT JOIN时ON只绑定紧邻右表
写 A LEFT JOIN B ON ... LEFT JOIN C ON ...,第二个 ON 只作用于 C 表,不能引用 A 字段做等值判断(除非显式写出 A.id = C.aid),也不能依赖 B 已被 WHERE 筛过——因为 WHERE 在所有连接完成后才执行。
常见误写:LEFT JOIN C ON B.id = C.bid WHERE C.flag = 1,这会把 B 有数据但 C 不满足 flag = 1 的整行干掉;而 ON B.id = C.bid AND C.flag = 1 才是让 C 按条件参与连接,A 和 B 的组合不受影响。
最容易被忽略的,是 ON 阶段右表还没被任何 WHERE 处理过,所以里面用的字段如果有大量 NULL 或类型不一致,可能让索引失效——这不是语法错,是隐性性能陷阱。

















