where中子查询返回null会导致整行被过滤,因col = null结果为unknown而非false;应改用exists或coalesce+limit 1兜底,避免not in含null导致全空,join比标量子查询更高效且可控。

WHERE里用子查询等号比较时,NULL会让整行消失
当你写 WHERE col = (SELECT id FROM users WHERE name = 'Alice'),而表里根本没叫 Alice 的用户,这个子查询返回 NULL。但 col = NULL 不是 FALSE,而是 UNKNOWN——WHERE 只保留 TRUE 行,所以这一行直接被过滤掉,查不到任何结果,连空提示都没有。
- 别补
AND (SELECT ...) IS NOT NULL:触发两次子查询,性能翻倍 - 改用
EXISTS:语义清晰且不依赖值,WHERE EXISTS (SELECT 1 FROM users u WHERE u.name = 'Alice' AND u.id = t.col) - 真要返回标量值,加
COALESCE+LIMIT 1:比如(SELECT COALESCE(MAX(id), 0) FROM users WHERE name = 'Alice' LIMIT 1),但得确认业务上 0 是合理兜底
NOT IN遇到NULL就整个失效,不是漏数据而是逻辑崩了
col NOT IN (SELECT code FROM status_ref) 看似安全,只要 status_ref.code 里有一条是 NULL,整个表达式变成 UNKNOWN,结果集必然为空——哪怕其他所有值都匹配,也一条不返回。
- 永远别信含
NULL的NOT IN,它不是慢,是错 - 换成
NOT EXISTS:例如WHERE NOT EXISTS (SELECT 1 FROM status_ref s WHERE s.code = t.col) - 如果必须用
IN,先排除NULL:SELECT code FROM status_ref WHERE code IS NOT NULL
标量子查询返回NULL会静默污染计算和拼接
SELECT id, (SELECT name FROM users WHERE id = orders.user_id) AS user_name FROM orders 这种写法,只要某条订单的 user_id 在 users 表里不存在,user_name 就是 NULL。后续做 CONCAT('Hi, ', user_name) 或 WHERE user_name = 'Alice' 全部中断,还不报错。
- 优先改用
LEFT JOIN:LEFT JOIN users u ON orders.user_id = u.id,再用COALESCE(u.name, 'Unknown') - 避免
IFNULL((SELECT ...), 'X'):N 行触发 N 次子查询,性能差一个数量级 -
COALESCE参数类型必须一致:比如COALESCE((SELECT price FROM items LIMIT 1), 0),不能混用字符串和数字,否则隐式转换可能把'N/A'变成0
聚合函数里NULL不是“跳过”那么简单,分母会悄悄变
AVG(price) 跳过 NULL,但 AVG(COALESCE(price, 0)) 把 NULL 当 0 算——两者分母完全不同。比如 5 条记录,2 条 price 是 NULL,前者算 3 个非空值的平均,后者算全部 5 条的平均。
- 查清基数再下手:
SELECT COUNT(*), COUNT(price), COUNT(NULLIF(price, 0)) FROM orders - 想排除 0(比如测试单),用
AVG(NULLIF(price, 0)) - 想把
NULL当 0 参与,明确写AVG(COALESCE(price, 0)),但得同步检查业务是否接受这种语义
WHERE、JOIN、IN、聚合 里触发的失效机制各不相同,但根源都是三值逻辑。不预判来源、不显式兜底、不换语义结构,光靠 IS NULL 补救,往往只解决表象。











