今天在查数据时,遇到了一个让我一度怀疑数据库出问题的“诡异”现象。
背景很简单:
我有两张表,t1 和 t2。
t1 表里有 1677 条记录,每条都有一个非空的 code 字段。
我想找出那些 code 不在 t2 表中的记录。
于是,我先试了下 IN:
SELECT * FROM t1 WHERE code IN (SELECT code FROM t2);
结果返回了 1675 条 —— 看起来没问题,说明有 2 条没匹配上。
那接下来自然就是用 NOT IN 找出这 2 条“漏网之鱼”:
SELECT * FROM t1 WHERE code NOT IN (SELECT code FROM t2);
结果……0 条?!
这不对啊!1677 - 1675 = 2,怎么 NOT IN 一条都捞不出来?
一开始我还以为是数据重复、类型不一致,甚至怀疑是不是缓存或者事务隔离的问题。但反复检查后发现,t1.code 都是干净的非空字符串,t2.code 看起来也正常。
直到我突然想起一个老生常谈但极易被忽略的坑:NOT IN 遇到 NULL 会失效。
原来,t2.code 字段里其实混进了几条 NULL 值。而 SQL 的逻辑是这样的:
如果子查询的结果中包含哪怕一个
NULL,那么NOT IN的整个条件就会变成“未知”(UNKNOWN),而 SQL 只返回WHERE条件为TRUE的行,UNKNOWN被当作FALSE处理 —— 所以最终一条都不返回。
举个例子:
SELECT * FROM t1 WHERE code NOT IN (SELECT code FROM t2);
等价于:
SELECT * FROM t1 WHERE code <> val1 AND code <> val2 AND ... AND code <> NULL;
而 code <> NULL 永远是 UNKNOWN,整个表达式也就永远不成立。
知道原因后,解决就简单了。只要在子查询里排除掉 NULL:
SELECT * FROM t1
WHERE code NOT IN (SELECT code FROM t2 WHERE code IS NOT NULL);
这次,稳稳地返回了 2 条,正是我想要的结果。
小结
IN对NULL不敏感,能正常工作;NOT IN对NULL极其敏感,一旦子查询含NULL,结果可能完全“消失”;- 更安全的替代方案还有
NOT EXISTS或LEFT JOIN ... IS NULL,它们天然规避了NULL陷阱。
这次踩坑再次提醒我:看似简单的 SQL,背后可能藏着逻辑陷阱。尤其是在处理来自 ETL、外部系统或历史遗留表的数据时,NULL 总是悄无声息地埋雷。
下次再写 NOT IN,我一定先问一句:子查询里有没有 NULL?
希望这篇记录能帮到同样被 NOT IN 背刺过的你。