JOIN 条件中 = 无法匹配两个 NULL,因为 SQL 三值逻辑下 NULL = NULL 返回 UNKNOWN 而非 TRUE,而 JOIN 仅保留条件为 TRUE 的行;IS NOT DISTINCT FROM 是 SQL:2003 标准操作符,语义为“值相等或两者同为 NULL”,PostgreSQL 和 MySQL 8.0.16+ 等部分数据库支持,SQL Server 和 Oracle 需手写等价逻辑 (a=b) OR (a IS NULL AND b IS NULL)。

为什么 JOIN 条件里 = 无法匹配两个 NULL
SQL 标准规定:NULL = NULL 返回 UNKNOWN,不是 TRUE,所以 ON a.id = b.id 在任一端为 NULL 时,整行被排除。这不是 bug,是三值逻辑的必然结果。常见于外键缺失、可选字段未填充、ETL 清洗不彻底等场景。
IS NOT DISTINCT FROM 是什么,哪些数据库支持
它是一个 SQL 标准(SQL:2003 起)的比较操作符,语义是“值相等或两者都为 NULL”,即把 NULL 当作一个确定的值来对待。但支持度不一:
- PostgreSQL:原生支持,可直接用于
ON、WHERE、ORDER BY - SQL Server:不支持,需用
(a.col = b.col) OR (a.col IS NULL AND b.col IS NULL) - MySQL:8.0.16+ 支持,但仅限于
WHERE和表达式上下文,JOIN条件中部分版本有优化限制 - SQLite:支持,行为稳定
- Oracle:不支持,必须手写等价逻辑
在 JOIN 中正确使用 IS NOT DISTINCT FROM
不能把它当成普通函数调用,它就是个二元操作符,语法和 = 一样,只是语义更强。错误写法包括:IS_NOT_DISTINCT_FROM(a.id, b.id)(不是函数)、a.id IS NOT DISTINCT FROM NULL(虽然合法,但通常应避免裸写 NULL,而用列对列)。
正确示例(PostgreSQL):
SELECT * FROM orders o JOIN customers c ON o.customer_id IS NOT DISTINCT FROM c.id;
这个 JOIN 会保留 o.customer_id 和 c.id 同时为 NULL 的行 —— 常见于“未知客户”与“未归类客户”主表关联场景。
注意事项:
- 索引可能失效:多数数据库不会为
IS NOT DISTINCT FROM自动生成索引优化,尤其是含NULL的复合条件 - 不能用于
USING子句,只能用于ON - 如果列定义为
NOT NULL,用它没意义,还可能干扰查询计划器
没有 IS NOT DISTINCT FROM 怎么安全降级
手写等价逻辑最稳妥,但要注意括号和运算符优先级。错误写法:a.col = b.col OR a.col IS NULL AND b.col IS NULL(AND 优先级高于 OR,实际等价于 a.col = b.col OR (a.col IS NULL AND b.col IS NULL),看似对,但一旦加了其他条件就容易出错)。
推荐写法(显式括号 + 统一风格):
ON (a.col = b.col) OR (a.col IS NULL AND b.col IS NULL)
更健壮的写法(尤其多字段时):
ON (a.key1, a.key2) IS NOT DISTINCT FROM (b.key1, b.key2) -- PostgreSQL 支持行级比较
若数据库连行级都不支持,就只能拆成:
ON (a.key1 = b.key1 OR (a.key1 IS NULL AND b.key1 IS NULL)) AND (a.key2 = b.key2 OR (a.key2 IS NULL AND b.key2 IS NULL))
真正麻烦的不是写法,而是团队里有人习惯性加 WHERE col IS NOT NULL 来“防错”,结果把本该关联上的 NULL 行全滤掉了 —— 这类隐式假设,比语法本身更难调试。










