标量子查询为空时表达式变为UNKNOWN导致行被静默丢弃,应优先用EXISTS替代;若必须用标量子查询,需用COALESCE包裹并确保默认值语义安全;IN/NOT IN遇NULL会整体失效,须过滤NULL或改用NOT EXISTS。

WHERE = (SELECT ...) 遇到空子查询直接失效
这种写法最危险:外层条件不是“匹配失败”,而是整个表达式变成 UNKNOWN,结果行被静默丢弃。比如 WHERE order.user_id = (SELECT id FROM users WHERE name = '不存在'),查不到任何记录,连空集提示都没有。
- MySQL 8.0+ 在严格模式下会报错
Subquery returns no rows;老版本可能隐式转为NULL,但 PostgreSQL、SQL Server 直接报错 - 别指望用
IS NULL捕获它——(SELECT ...)整体不能跟IS NULL连用,语法不合法 - 重复写两次子查询(一次判非空、一次取值)性能差,且仍存在执行两次的竞态风险
用 EXISTS 替代等值子查询才是通用解法
EXISTS 不依赖返回值,只关心“是否存在一行满足条件”,天然绕过 NULL 和三值逻辑陷阱,所有主流数据库都支持。
- 正确写法:
WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = orders.user_id AND u.name = '张三') - 子查询里必须带对外层表的关联条件(如
u.id = orders.user_id),否则变成全表扫描 - 统一用
SELECT 1,避免传输冗余字段;优化器对EXISTS的执行计划通常比SELECT *更优 - 多个条件就拆成多个
EXISTS并用AND连接,互不影响
非要标量子查询?COALESCE 必须紧贴括号包裹
标量子查询没结果时,整条表达式直接变 NULL,会“传染”到算术、拼接、比较中。比如 price * (SELECT tax_rate FROM taxes WHERE id = 1),只要子查询空,结果就是 NULL,不是原价。
- 必须写成
COALESCE((SELECT tax_rate FROM taxes WHERE id = 1), 0),而不是(SELECT COALESCE(tax_rate, 0) FROM ...) -
COALESCE只兜最内层子查询,不能跨层;参数类型必须一致,否则报错 - 用
COALESCE(subquery, 0) = 0判断“子查询无结果”是错的——万一真有id = 0的合法记录呢? - 业务上真要默认值,得确认该值在主表中确实存在且语义安全(比如
customer_id = 0表示“游客订单”)
IN / NOT IN 子查询里混入 NULL 就全崩
WHERE status IN (SELECT s FROM ref),只要 ref.s 里有一条是 NULL,整个条件恒为 UNKNOWN,结果为空——这不是 bug,是 SQL 标准行为,但极难排查。
- 根本原因:
status IN (1, 2, NULL)等价于status = 1 OR status = 2 OR status = NULL,最后一项永远是UNKNOWN - 安全写法:加
WHERE s IS NOT NULL过滤子查询输出,或改用NOT EXISTS替代NOT IN - MySQL 不支持
IN () OR () IS NULL这类绕过写法,跨库方案必须统一用EXISTS -
LIKE同样不能直接受多行子查询,必须用EXISTS + LIKE或JOIN实现动态模糊匹配
真正容易被忽略的是:标量子查询的“空结果”和字段本身的 NULL 是两回事,前者让整个表达式坍缩为 NULL,后者只是单个值。你得先决定——是要判断“有没有匹配记录”,还是“匹配记录的字段是不是 NULL”。选错路径,后面所有 COALESCE 或 IS NULL 都在补一个错误的前提。

















