ON 与 WHERE 的区别是什么
内连接里 ON 与 WHERE 可以互换,优化器通常会把它们合并处理;外连接里不能互换:ON 决定两表如何配对,WHERE 在配对结果上过滤。把右表条件写进 WHERE,会把右表补 NULL 出来的行一并过滤掉,外连接静默退化成内连接,数据变少却不报错。左表条件写在 ON 还是 WHERE 结果相同,但写在 WHERE 语义更清楚。多表外连接串联时,每一步的 ON 与 WHERE 位置都要单独确认。
基本信息
| 内连接 | ON 与 WHERE 语义等价,写法可互换 |
|---|---|
| 外连接 ON | 决定配对规则;右表未匹配的行仍保留并补 NULL |
| 外连接 WHERE | 在配对结果上过滤,会删掉补 NULL 的行 |
| 左表条件 | 写 ON 或 WHERE 结果相同,WHERE 语义更清晰 |
| 多表串联 | 每一级外连接的右表条件都要放在对应的 ON 里 |
| 适用平台 | 标准 SQL 语义,MySQL 8.0 与 PostgreSQL 一致 |
典型使用场景
- 外连接限定匹配范围:只匹配已支付订单,但仍保留所有用户
- 外连接过滤左表:只要某地区的用户,条件写 WHERE
- 排查外连接退化:结果行数比左表还少时,先看 WHERE 里有没有右表条件
- 多级左连接:A 左连 B 再左连 C,B 与 C 的过滤条件分别放各自的 ON
- 反连接:WHERE 右表主键 IS NULL 专门保留未匹配行
- 报表补零:日期维表左连事实表,事实侧的过滤条件必须写在 ON
两种语义对比
两种语义对比
-- 语义 A:只匹配已支付订单,但保留所有用户(条件写在 ON)
SELECT u.id, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status = 'paid';
-- 语义 B:只要下过已支付订单的用户(条件写在 WHERE,退化为内连接)
SELECT u.id, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid';
-- 左表条件写在 WHERE,不影响外连接语义
SELECT u.id, o.amount
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.city = 'Beijing';条件位置对照
| 场景 | 写在 ON | 写在 WHERE |
|---|---|---|
| 右表条件(外连接) | 保留左表全部行,未匹配补 NULL | 补 NULL 的行被丢弃,退化为内连接 |
| 左表条件(外连接) | 与写在 WHERE 结果相同 | 语义更明确,推荐 |
| 连接之外的非等值附加条件 | 常用,用于收窄匹配范围 | 等价,可读性略差 |
| 内连接的两表条件 | 与 WHERE 等价 | 与 ON 等价 |
| 反连接判断 | 不能实现 | WHERE 右表主键 IS NULL |
判断写法是否正确有一个快检:外连接的结果行数不应该少于左表行数,比左表还少就说明右表条件写错了位置。
右表条件写在 WHERE 是外连接最常见的错误:结果静默变成内连接,数据少了也不报错,排查时先对比两边行数。
把连接条件与过滤条件分开写:ON 只写两表之间的关系,WHERE 只写结果集要满足的条件,可读性最好,也最不容易改错。
USING(col) 是 ON a.col = b.col 的简写,结果里该列只出现一次;跨库复用时注意部分数据库对 USING 的支持差异。
实践要点
- 写外连接时先问一句:这张表的过滤是要收窄匹配,还是要筛掉结果行
- 收窄匹配放 ON,筛结果放 WHERE,两者混写是 bug 的高发区
- 多级外连接逐段验证行数,每一步都确认没有意外过滤或放大
- 用注释标明 ON 里条件的意图,避免后人把它挪到 WHERE
- 结果行数异常时先数左表行数,再数连接后的行数
- 反连接优先用 NOT EXISTS,语义不受 NULL 影响
三张表以上的连接里判断条件属于谁,看它引用了哪张表的列:只引用右表的过滤条件放 ON,引用多表的比较条件本来就是连接条件的一部分。