LEFT JOIN 后WHERE过滤会变INNER JOIN,因WHERE在连接后全局过滤,会筛掉右表为NULL的行;正确做法是将右表字段过滤条件放入对应ON子句。

LEFT JOIN 后 WHERE 过滤会变 INNER JOIN
这是最常踩的坑:明明写了 LEFT JOIN,结果右表没匹配的行全丢了。原因就是把本该放在 ON 子句里的过滤条件错写进了 WHERE。
比如想查所有用户及其订单数,但只统计「2024 年的订单」——如果写成:
SELECT u.id, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.created_at >= '2024-01-01'
效果等同于 INNER JOIN,因为 WHERE 会筛掉所有 o.created_at 为 NULL 的行(即无订单用户)。
-
ON是连接时的逻辑:决定哪些右表行能连上来 -
WHERE是连接完成后的全局过滤:对最终结果集生效 - 右表字段的非空判断(如
o.status IS NOT NULL)必须放ON才保留左表空匹配
INNER JOIN 中 ON 和 WHERE 可互换?不完全
语义上,INNER JOIN 的 ON 和 WHERE 确实常可互换,但有两点实际差异:
- 可读性:关联条件放
ON,业务筛选放WHERE更清晰 - 执行计划:某些数据库(如 MySQL 5.7 前)对
WHERE中的关联字段可能无法下推到连接阶段,影响性能 - 如果涉及函数或表达式(如
ON u.id = CAST(o.user_id AS INT)),放WHERE可能导致索引失效
多表 JOIN 时 WHERE 条件的位置更关键
三张表连查:users → orders → order_items,若想只统计「含电子产品订单的用户」,错误写法是:
SELECT u.name FROM users u LEFT JOIN orders o ON u.id = o.user_id LEFT JOIN order_items oi ON o.id = oi.order_id WHERE oi.category = 'electronics'
这会让所有没买电子产品的用户彻底消失。正确做法是把分类条件收进第二层 ON:
LEFT JOIN order_items oi ON o.id = oi.order_id AND oi.category = 'electronics'
- 每层
LEFT JOIN的过滤条件,只要涉及右表字段,都该优先塞进对应ON -
WHERE只留真正需要全局过滤的字段(如u.status = 'active') - 用
EXPLAIN看执行计划时,注意filtered值突降,往往就是WHERE错位导致中间结果被提前裁剪
NULL 安全比较在 WHERE 里容易失效
当右表字段可能为 NULL(如 LEFT JOIN 后的 o.amount),用 WHERE o.amount != 100 会漏掉 NULL 行——因为 NULL != 100 返回 UNKNOWN,被过滤掉。
- 要包含
NULL,得显式写WHERE o.amount != 100 OR o.amount IS NULL - 或者用
IS DISTINCT FROM(PostgreSQL)或COALESCE(o.amount, -1) != 100(通用) - 这个陷阱在报表类查询中高频出现,尤其当字段有默认值或允许为空时
边界不是语法限制,而是执行顺序带来的语义偏移:JOIN 先按 ON 搭桥,WHERE 再对整张“拼好的表”动刀。一旦右表字段参与了 WHERE,就等于悄悄给左连接加了锁链。










