ON 先过滤,WHERE 后过滤;ON 决定哪些行参与连接,影响中间结果集大小,WHERE 作用于连接后的临时表,可能使 LEFT JOIN 退化为 INNER JOIN。

WHERE 和 ON 的执行顺序到底谁先过滤?
JOIN 查询里加条件,WHERE 和 ON 看起来都能筛数据,但它们生效时机完全不同:后者在连接过程中起作用,前者在连接完成后再过滤。这意味着把本该写在 ON 里的关联条件错放 WHERE,可能让 LEFT JOIN 变成 INNER JOIN——这是最常踩的坑。
-
ON中的条件决定「哪些行能参与连接」,影响中间结果集大小 -
WHERE中的条件作用于连接后的整张临时表,会直接剔除不满足的整行(包括左表原本该保留的 NULL 行) - 多表 JOIN 时,
ON是紧挨着对应JOIN关键字的,不能跨表引用未引入的表别名
LEFT JOIN + WHERE 条件导致丢失左表数据?
典型症状:明明用了 LEFT JOIN,结果里左表某些行却没了。问题往往出在 WHERE 子句里写了右表字段的非空判断,比如 WHERE t2.status = 'active'。这时数据库会先完成 LEFT JOIN(右表匹配不到就补 NULL),再执行 WHERE——而 NULL = 'active' 为 false,整行被干掉。
- 想保留左表所有行,又只取右表符合条件的部分,必须把条件挪到
ON:LEFT JOIN t2 ON t1.id = t2.t1_id AND t2.status = 'active' - 如果右表条件是
IS NULL类型(如查没关联记录的左表行),可以放心放WHERE,比如WHERE t2.id IS NULL - MySQL 5.7+ 对
WHERE中右表字段的非空判断有隐式转换警告,但不会报错,容易误判
ON 子句里能用复杂表达式吗?
能,但要注意兼容性和可读性。几乎所有主流 SQL 引擎(PostgreSQL、MySQL、SQL Server)都支持在 ON 里写函数、运算符甚至子查询(只要不相关),但性能和语义容易出问题。
- 推荐只在
ON里用简单等值或范围比较,比如t1.code = UPPER(t2.code)或t1.ts >= t2.start_time - 避免在
ON中调用高开销函数(如JSON_EXTRACT、正则匹配),它会在每一对连接候选行上重复执行 - PostgreSQL 允许
ON中写EXISTS子查询,但 MySQL 不支持;SQLite 则对非等值ON条件优化较差
WHERE 与 ON 混用时的执行计划怎么看?
光看 SQL 写法不够,得看执行计划里实际怎么走。不同数据库对条件下推的策略差异很大,尤其是涉及索引和统计信息时。
- 用
EXPLAIN(MySQL/PostgreSQL)或SET SHOWPLAN_ALL ON(SQL Server)确认ON条件是否触发了索引查找,而不是全表扫描 - 如果
WHERE中有左表字段的过滤(如t1.created_at > '2024-01-01'),多数引擎会先走左表索引再 JOIN,这时候放WHERE反而更高效 - 当
ON包含多个条件时,注意数据库是否能把所有条件都用于连接算法选择(比如 MySQL 的 Block Nested-Loop vs Hash Join)
真正难的不是语法对错,而是理解你的数据分布和引擎如何拆解这个 JOIN——同一段 SQL,在小表和大表上可能触发完全不同的执行路径。