NOT IN 的 NULL 陷阱:一次 UNION 数据"神秘消失"

昨天写了个 SQL,逻辑明明没毛病,数据就是查不出来。排查半天,元凶竟是 NULL。

一、现象:我的数据呢?

先看我写的 SQL,很简单:

SQL 复制代码
WITH tmp_union3 AS (
    SELECT * FROM tmp_union
    UNION ALL
    SELECT * FROM tmp_union2
    WHERE id NOT IN (
        SELECT DISTINCT id FROM tmp_union
    )
)
SELECT * FROM tmp_union3 WHERE id = '23009724';

已知:

  • tmp_union 里没有 '23009724'
  • tmp_union2 里明明有 '23009724'

按道理,NOT IN 应该放行,然后 UNION ALL 合并,最后查出来。

结果:查无此记录。

二、排查:问题出在哪?

先单独跑 NOT IN 这一段:

SQL 复制代码
SELECT * FROM tmp_union2
WHERE id NOT IN (SELECT DISTINCT id FROM tmp_union);

返回:空结果集。

'23009724' 明明不在 tmp_union 里,凭啥被过滤了?

再查一下 tmp_union 的数据:

SQL 复制代码
SELECT id FROM tmp_union;

破案了------tmp_union 里有 NULL。

三、原理:一个 NULL,全军覆没

在 PostgreSQL 里,没有unknown,只有null,NULL 代表"未知"

当 NOT IN 的右侧出现 NULL 时:

SQL 复制代码
'2026' NOT IN (NULL, '2025', '2026')

数据库实际比较:

SQL 复制代码
'2026' = '2025'  → FALSE
'2026' = NULL        → NULL   -- 不是 FALSE,是 NULL!

关键点来了:

NOT IN 的本质是 AND 串联的等值比较

Plain 复制代码
'2026' <> NULL AND '2026' <> '2025' AND ...

在 PG 里,NULL AND FALSE 的结果是 NULL,NULL AND TRUE 也是 NULL。

而 WHERE 子句只认 TRUE,NULL 被当成不满足条件。

所以只要 NOT IN 的右侧集合里有一个 NULL,整个条件就塌了,所有行都被过滤。

不是一个,是全部。这就是黑洞。

四、修复:三选一

方法 1:过滤 NULL(改动最小)

SQL 复制代码
WHERE id NOT IN (
    SELECT DISTINCT id FROM tmp_union
    WHERE id IS NOT NULL  -- 加这一行
)

方法 2:改用 NOT EXISTS(推荐)

SQL 复制代码
SELECT * FROM tmp_union2 t2
WHERE NOT EXISTS (
    SELECT 1 FROM tmp_union t1
    WHERE t1.id = t2.id
);

NOT EXISTS 不受 NULL 影响,而且性能通常更好。

方法 3:LEFT JOIN 取差集

SQL 复制代码
SELECT t2.*
FROM tmp_union2 t2
LEFT JOIN tmp_union t1 ON t1.id = t2.id
WHERE t1.id IS NULL;

五、口诀

看到 NOT IN,先防 NULL;能写 EXISTS,别写 IN

六、总结

这次踩坑就一句话:

NOT IN 遇到 NULL,不是漏几条,是全部消失。

而且不会报错,悄无声息。

你以为是逻辑问题,其实是 NULL 在搞鬼。

以后写 SQL,遇到 NOT IN 多长个心眼,或者直接换成 NOT EXISTS,一劳永逸。

你遇到过类似的 NULL 陷阱吗?评论区聊聊,一起排雷。

相关推荐
小满zs4 小时前
Go语言第十七章(反射)
后端·go
小满zs4 小时前
Go语言第十五章(JSON序列化)
后端·go
小满zs4 小时前
Go语言第十六章(自定义类型)
后端·go
金銀銅鐵5 小时前
[Java] 用 GUI 展示 EnumMap 增删键值对时的内部变化
java·后端·ai编程
学长毕设5 小时前
基于SpringBoot的上门洗车服务系统设计与实现
java·spring boot·后端
小溪学编程8 小时前
Spring @Bean 与 @Component 注解的区别详解
java·后端·spring
IT_陈寒8 小时前
Python的列表推导式用不好真的会爆内存你知道吗
前端·人工智能·后端
摸爬滚打的小李10 小时前
TomCat
后端
itmigrate10 小时前
Rust 里的所有权误区:不是不写 clone 就叫高性能
开发语言·后端·rust
码事漫谈10 小时前
Deepseek v5 来了
后端