标量子查询为空时外层表达式变为unknown被where过滤,应优先用exists替代;in/not in遇null失效,须改用exists/not exists;null比较必须用is null,聚合计算前需coalesce。

标量子查询返回空结果时,外层 = 或 IN 表达式不会变 NULL,而是整体变成 UNKNOWN,被 WHERE 静默过滤——这不是 bug,是 SQL 三值逻辑的必然行为。直接加 IS NULL 判断子查询本身语法不合法,必须换写法。
WHERE col = (SELECT ...) 失效时该用 EXISTS 替代
这种写法最危险:子查询无结果,整行消失,连报错或空提示都没有。MySQL 8.0+ 严格模式下会报 Subquery returns no rows,老版本或 PostgreSQL/SQL Server 可能直接报错或静默失败。
-
EXISTS不依赖子查询返回值,只判断“是否存在满足条件的行”,天然绕过NULL和三值逻辑陷阱 - 子查询里必须带对外层表的关联条件,比如
WHERE u.id = orders.user_id AND u.name = '张三',否则变成全表扫描 - 统一用
SELECT 1,优化器更易优化;多个条件就拆成多个EXISTS并用AND连接 - 错误示例:
WHERE order.user_id = (SELECT id FROM users WHERE name = '不存在')→ 查不到任何记录 - 正确示例:
WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = orders.user_id AND u.name = '张三')
非要用标量子查询?COALESCE 必须紧贴括号包裹
标量子查询没结果时,整个表达式直接变 NULL,会“传染”到算术、拼接、比较中。比如 price * (SELECT tax_rate FROM taxes WHERE id = 1),只要子查询空,结果就是 NULL,不是原价。
-
COALESCE必须写在最外层括号内:COALESCE((SELECT tax_rate FROM taxes WHERE id = 1), 0),不能写成(SELECT COALESCE(tax_rate, 0) FROM ...) - 参数类型必须一致,否则报错;
COALESCE(subquery, 0) = 0不能用来判断“子查询无结果”,因为 0 可能是合法值 - 若业务上允许默认值,得确认该值在语义上安全(如
customer_id = 0明确表示“游客”) - 加
LIMIT 1强制标量,避免多行报错:(SELECT COALESCE(MAX(id), 0) FROM users WHERE name = '张三' LIMIT 1)
IN / NOT IN 遇到子查询含 NULL 就全崩
col IN (SELECT x FROM t) 看似简单,但只要子查询结果里有一个 NULL,整个表达式就变成 UNKNOWN,这一行必然被过滤掉——不是漏数据,是整批失效。
- 根本原因:
status IN (1, 2, NULL)等价于status = 1 OR status = 2 OR status = NULL,最后一项永远是UNKNOWN - 安全写法:子查询加
WHERE x IS NOT NULL过滤输出,或直接改用EXISTS关联:EXISTS (SELECT 1 FROM status_ref s WHERE s.code = o.status) -
NOT IN更危险,运维中常见翻车:SELECT * FROM orders WHERE user_id NOT IN (SELECT id FROM users)返回空,其实是有孤儿订单,只是users.id含NULL - 一律改用
NOT EXISTS,语义等价、不受NULL影响、还能走索引
触发器和存储过程中 NULL 比较必须用 IS NULL
在触发器或存储过程里写 IF NEW.phone = NULL THEN 或 IF var_name IN ('a','b'),当字段为 NULL 时,整个条件判定为 UNKNOWN,分支完全不执行——安静失效,极难排查。
-
= NULL、!= NULL、IN对NULL永远返回UNKNOWN,IF只响应TRUE - 唯一可靠写法是
IS NULL或IS NOT NULL,它是原子判断,不触发隐式转换,索引也能用 - 聚合计算前必须用
COALESCE(amount, 0)而不是裸写amount + ...,否则整列变NULL -
SELECT ... INTO @var遇空结果集,变量不会清空,仍保留上次值;必须显式初始化:SET @var = NULL
最容易被忽略的是:子查询是否真需要返回值,还是只需要存在性判断。多数场景下,用 EXISTS 不仅修复了 NULL 问题,还让语义更清晰、执行计划更优。硬套标量子查询再层层 COALESCE,往往掩盖了设计缺陷——比如本该用 LEFT JOIN 关联的,却用五层嵌套子查询硬撑。










