LEFT JOIN中右表条件须写在ON而非WHERE,否则空匹配行被过滤;ON限制连接匹配,WHERE裁剪最终结果;如需保留所有左表行且仅关联满足条件的右表行,条件必须置于ON子句。

LEFT JOIN 中 ON 和 WHERE 过滤右表字段,结果天差地别
LEFT JOIN 里对右表字段加条件,写在 ON 还是 WHERE,直接决定左表空匹配行是否保留。写错位置,LEFT JOIN 就悄悄变成 INNER JOIN。
常见错误现象:SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid' —— 结果里压根没有没订单的用户,连“用户存在但无支付订单”这种合理情况都被砍掉了。
-
ON中的条件只参与匹配:它限制的是“哪些右表行能跟当前左表行连上”,不满足就填NULL,左表行照出 -
WHERE中的条件作用于最终结果集:只要o.status = 'paid'为FALSE或NULL(比如没订单时o.status是NULL),整行就被丢弃 - 想保留所有用户、只连上已支付的订单?把状态条件挪进
ON:ON u.id = o.user_id AND o.status = 'paid'
INNER JOIN 下 ON 和 WHERE 看似等效,但语义不能混用
INNER JOIN 查询中,ON a.id = b.id AND b.deleted = 0 和 ON a.id = b.id WHERE b.deleted = 0 执行结果通常一样,但这只是优化器“帮忙重写”的巧合,不是 SQL 标准保证的行为。
容易踩的坑:
- 把业务筛选(如
b.deleted = 0)写进ON,后续想改成LEFT JOIN时必须同步改逻辑,否则语义断裂 - 跨数据库迁移时行为可能不一致:某些老版本 MySQL 或 Presto 不会等价重写,
WHERE条件下推时机不同,结果出错 - 多表 JOIN 时,
ON中混入非关联字段(如AND b.category = 'A')会让执行计划难以优化,中间结果集膨胀
ON 条件里能写左表字段吗?可以,但得清楚它在做什么
可以写,比如 LEFT JOIN orders o ON u.id = o.user_id AND u.type = 'vip',但这个 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,不能靠猜顺序
写 A LEFT JOIN B ON ... LEFT JOIN C ON ... 时,第二个 ON 只属于第二个 JOIN,它能引用 A 和 B 的字段,但若依赖 B 经过 WHERE 过滤后的结果(比如 B 已被筛掉部分行),就不能指望 C 的连接还按预期工作。
真实场景中的坑:
- 误以为
A LEFT JOIN B ON a.x = b.x LEFT JOIN C ON b.y = c.y AND a.z = c.w中的a.z = c.w总能生效 —— 实际上它有效,但可读性差,且当 B 行数被前置条件大幅削减后,C 的连接基数估算容易失真 - 复杂过滤(如时间范围、权限校验)应尽量前置:用子查询或 CTE 先筛好 B,再 JOIN C,比堆在 ON 里更可控
- MySQL 8.0+ 支持
LATERAL,但多数 OLAP 场景仍建议拆成明确步骤,避免优化器“自作聪明”
关键点在于:ON 是连接动作的一部分,WHERE 是连接完成后的裁剪。很多人卡在“以为 ON 是提前过滤”,其实它是“带条件匹配”。一旦涉及 LEFT JOIN 和右表字段,这个区别就是数据有无的分水岭。

















