LEFT JOIN 的 ON 子句中加 AND 条件只过滤右表匹配逻辑,不影响左表行保留;WHERE 则全局过滤最终结果,会剔除右表为 NULL 的左表行。

LEFT JOIN 的 ON 子句里加 AND 条件,到底过滤哪边?
在 LEFT JOIN 中把过滤条件写进 ON 子句(用 AND 连接),和写进 WHERE 子句,行为完全不同——前者影响的是「右表是否能匹配上」,后者影响的是「最终结果是否保留该行」。这是最容易混淆、也最常导致数据丢失的点。
典型错误是:想只查出左表中某类用户(比如 status = 'active')对应的订单,却把 orders.status = 'paid' 写在 WHERE 里,结果把没订单的活跃用户也剔除了。
什么时候必须把条件放进 ON 而不是 WHERE?
当你需要保留左表所有行,但只希望右表满足某些条件时才关联,就必须把右表的过滤条件放在 ON 里。否则,WHERE 会把右表为 NULL 的整行干掉。
-
LEFT JOIN orders ON users.id = orders.user_id AND orders.status = 'paid'→ 活跃用户仍在,没支付订单的用户对应orders.*全为NULL -
LEFT JOIN orders ON users.id = orders.user_id WHERE orders.status = 'paid'→ 所有orders.status为NULL的行被过滤,只剩有支付订单的用户 - 如果右表条件涉及左表字段(如
orders.created_at > users.registered_at),也只能放ON;放WHERE会导致语法错误或意外过滤
ON 里混用左表字段 + AND 条件,会有啥副作用?
可以写,但要小心语义变化。例如:LEFT JOIN logs ON users.id = logs.user_id AND users.type = 'premium',这会让所有非 premium 用户的 logs 都无法匹配(即 logs.* 全为 NULL),但用户本身还在结果里——这有时正是你要的,比如“只尝试关联 premium 用户的日志”。
但如果你本意是“查所有用户,只对 premium 用户额外加载日志”,那就没问题;可如果误以为这个 AND 是全局筛选,就容易误解结果集规模。
- 左表字段在
ON中参与连接逻辑,不等于过滤左表;它只是让连接更“挑剔” - 执行计划里,这类条件可能推迟到 join 后再计算,部分数据库(如 MySQL 5.7)甚至可能无法下推,影响性能
- PostgreSQL 和 SQL Server 对
ON中左表条件支持更稳定;MySQL 在旧版本中偶有优化器误判
一个真实易错场景:统计带状态限制的关联计数
想统计每个用户有多少个「已发货」订单,但又不能漏掉零个订单的用户。下面这个写法是错的:
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'shipped' -- ❌ 这里砍掉了无订单用户 GROUP BY u.id;
正确做法是把状态条件移进 ON:
SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'shipped' -- ✅ GROUP BY u.id;
注意:COUNT(o.id) 自动忽略 NULL,所以没匹配上的用户计数就是 0。换成 COUNT(*) 就会变成 1,意义完全不同。
真正麻烦的是嵌套多层 LEFT JOIN 时,每一层的 ON 条件都得单独审视——少一个 AND,就可能让整条链路的语义崩掉。










