非等值连接中,条件必须放在on子句而非where中;on决定如何连接表并保留外连接语义,where则在连接后过滤结果,错误放置会导致漏数据或笛卡尔积。

非等值连接必须用 ON 而不是 WHERE 放条件
很多人写 JOIN 时把非等值条件塞进 WHERE,结果查出来是笛卡尔积或漏数据。非等值连接的条件(比如 BETWEEN、、<code>!=)**必须写在 ON 子句里**,否则会被当成过滤主结果集的后置条件,失去连接语义。
典型错误写法:SELECT e.name, g.grade_level FROM employees e JOIN job_grades g WHERE e.salary BETWEEN g.lowest_sal AND g.highest_sal;
这实际是先做笛卡尔积,再过滤——性能爆炸,且 MySQL 8.0+ 可能直接报错(严格模式下不允许多表无 ON 条件)。
正确写法:SELECT e.name, g.grade_level FROM employees e JOIN job_grades g ON e.salary BETWEEN g.lowest_sal AND g.highest_sal;
-
ON定义“两张表怎么配对”,WHERE是“配对完再筛谁留下” - 如果同时有等值和非等值条件,
ON里可以混写,比如ON e.dept_id = d.id AND e.salary > d.avg_salary - MySQL 不支持
FULL OUTER JOIN,但非等值连接本身在INNER、LEFT、RIGHT中都可用
BETWEEN 在非等值连接中是闭区间,但不能处理 NULL
BETWEEN a AND b 等价于 >= a AND ,包含端点值。这点在工资等级、价格区间、日期分段等场景很实用,但要注意它对 <code>NULL 完全失效。
比如:ON e.salary BETWEEN g.lowest_sal AND g.highest_sal
若 g.lowest_sal 或 g.highest_sal 任一为 NULL,整条匹配结果为 UNKNOWN,该行不会被关联上——哪怕 e.salary 是有效数字。
- 安全做法:提前过滤掉
NULL边界,如ON g.lowest_sal IS NOT NULL AND g.highest_sal IS NOT NULL AND e.salary BETWEEN g.lowest_sal AND g.highest_sal - 用半开区间更可控?改写为
e.salary >= g.lowest_sal AND e.salary (数值型)或显式用 <code>COALESCE补默认值 -
BETWEEN字符串比较依赖排序规则,'A' BETWEEN 'a' AND 'z'在大小写敏感 collation 下可能不成立
用 != 或 做非等值连接要防全表扫描
写 ON a.id != b.id 看似简单,但数据库很难走索引——因为不等于条件无法利用 B+ 树的有序性做范围跳转,大概率触发嵌套循环(Nested Loop)并扫描右表全量。
常见场景如“查不同部门的员工组合”或“排除自身关联”,性能极易崩:
- 避免直接
ON t1.id != t2.id,优先考虑是否能转成等值 + 排除逻辑,例如用LEFT JOIN ... ON t1.group_id = t2.group_id AND t1.id != t2.id再加WHERE t2.id IS NULL实现反向查找 - 如果真需要不等值配对,给参与比较的字段建联合索引(如
(group_id, id)),让优化器有机会用索引快速定位“同组但不同ID”的候选集 - 注意 MySQL 对
!=在ON中的支持较弱,某些版本会退化为 Block Nested Loop,建议用EXPLAIN确认type是ref还是ALL
LEFT JOIN 配非等值条件时 NULL 行容易被意外过滤
左连接本意是保留左表所有行,但如果非等值条件写在 WHERE,会导致左表原本该补 NULL 的行被整个踢出——因为 WHERE 会把 NULL 判为 FALSE。
错误示例:SELECT s.name, g.grade_level FROM students s LEFT JOIN grades g ON s.score = g.min_score WHERE s.score BETWEEN g.min_score AND g.max_score;
这里 WHERE 引用右表字段,会使所有 g 为 NULL 的行(即没匹配到等级的学生)被过滤掉,失去左连接意义。
- 正确做法:把范围条件也放进
ON,如ON s.score BETWEEN g.min_score AND g.max_score - 如果必须在
WHERE加额外筛选(比如只看 A 级学生),应写成WHERE g.grade_level = 'A' OR g.grade_level IS NULL,显式容错 - 尤其注意
NOT IN类逻辑不能用于ON,它会导致左表行全丢;改用NOT EXISTS子查询更稳妥
ON 里的每个不等号都在悄悄决定要不要扫全表、能不能用索引、NULL 该不该出现。写完务必用 EXPLAIN 看 rows 和 Extra 字段。










