ON控制如何连接表,WHERE控制连接后筛选结果;LEFT JOIN中ON条件影响行数和NULL分布,WHERE引用右表字段可能使LEFT JOIN退化为INNER JOIN。

JOIN 条件写在 ON 还是 WHERE,关键差别在于:前者控制**如何连接表**,后者控制**连接后如何筛选结果**;对 LEFT JOIN / RIGHT JOIN 来说,这个差别会直接影响结果集的行数和 NULL 值分布。
ON 中的条件决定“哪些行能被连上”
ON 子句在连接过程中起作用。数据库先根据 ON 条件匹配左右表的行,再生成中间结果(比如 LEFT JOIN 会保留左表所有行,右表不匹配的补 NULL)。
例如:
SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid';
这里 o.status = 'paid' 在 ON 中,意味着:只把用户对应的“已支付订单”连上来;没支付的订单不参与连接,但用户仍保留(amount 为 NULL)。
WHERE 中的条件是对连接结果整体过滤
WHERE 在 JOIN 完成、临时结果集生成之后才执行。它会把不满足条件的整行(包括左表数据)直接剔除。
同样逻辑,如果写成:
SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid';
由于 o.status 来自右表,而 LEFT JOIN 中不匹配的订单行里 o.status 是 NULL,NULL ≠ 'paid',所以这些用户行会被 WHERE 过滤掉——结果等价于 INNER JOIN,丢失了“没下单”或“下单未支付”的用户。
INNER JOIN 下二者常等效,但语义不同
对 INNER JOIN,ON 和 WHERE 的最终结果通常一样(只要逻辑条件不变),因为不匹配的行本来就不会出现在结果中。
不过仍建议:
- 把“关联关系”类条件(如
u.id = o.user_id)放 ON - 把“业务筛选”类条件(如
o.created_at > '2024-01-01')按意图放 ON 或 WHERE:想排除某些连接可能性就放 ON;想先连完再筛就放 WHERE - 保持逻辑清晰,避免后期误改导致 LEFT JOIN 行为突变
一个典型陷阱:LEFT JOIN + WHERE 右表字段
这是最易出错的场景。只要 WHERE 中引用了右表字段(且非 IS NULL/IS NOT NULL 判断),就可能把 LEFT JOIN “降级”为 INNER JOIN。
比如想查“所有用户及其最近一笔已支付订单”,错误写法:
SELECT u.name, o.amount FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid' AND o.id = ( SELECT MAX(id) FROM orders o2 WHERE o2.user_id = u.id );
这个 WHERE 实际排除了所有没有已支付订单的用户。正确做法是把业务条件尽量移入 ON,或用子查询/窗口函数预处理。










