一条 NULL 为什么让 NOT IN 查不到任何行
一份排除名单混入 NULL 后,WHERE id NOT IN (...) 可能过滤掉原本要保留的所有行。对未匹配的 ID,SQL 无法断言它与未知值不同,于是得到未知。查找没有匹配记录的行时,可以用相关子查询 NOT EXISTS 表达意图,并单独决定左侧空值是否保留。
1. 本机复现
保存为 null_lab.py,运行 python null_lab.py。左侧有三个客户和一条没有客户 ID 的记录,右侧有一条明确排除的记录和一个空值。
from contextlib import closing
import sqlite3
with closing(sqlite3.connect(":memory:")) as db:
db.executescript("""
CREATE TABLE incoming (customer_id INTEGER);
CREATE TABLE blocked (customer_id INTEGER);
INSERT INTO incoming VALUES (1), (2), (3), (NULL);
INSERT INTO blocked VALUES (2), (NULL);
""")
result = db.execute("""
SELECT customer_id, customer_id NOT IN (SELECT customer_id FROM blocked)
FROM incoming ORDER BY customer_id
""").fetchall()
assert result == [(None, None), (1, None), (2, 0), (3, None)]
print("NOT IN as a value:", result)
filters = {
"NOT IN": "i.customer_id NOT IN (SELECT customer_id FROM blocked)",
"NOT EXISTS": """NOT EXISTS (
SELECT 1 FROM blocked b WHERE b.customer_id = i.customer_id
)""",
"NOT EXISTS, ID required": """i.customer_id IS NOT NULL AND NOT EXISTS (
SELECT 1 FROM blocked b WHERE b.customer_id = i.customer_id
)""",
}
expected = [[], [None, 1, 3], [1, 3]]
for (label, condition), want in zip(filters.items(), expected):
rows = db.execute(f"""
SELECT i.customer_id FROM incoming i
WHERE {condition} ORDER BY i.customer_id
""").fetchall()
ids = [row[0] for row in rows]
assert ids == want
print(label + ":", ids)
本地实测输出如下。
NOT IN as a value: [(None, None), (1, None), (2, 0), (3, None)]
NOT IN: []
NOT EXISTS: [None, 1, 3]
NOT EXISTS, ID required: [1, 3]
2. 先定义两侧 NULL 的业务含义
SQLite 查询结果中的 None 对应 SQL NULL。对 ID 1 来说,名单里的 2 能排除,但比较名单里的 NULL 得不到真或假。WHERE 只保留条件为真的行,所以它与命中名单的 ID 2 一起消失。NOT EXISTS 检查子查询有没有返回匹配行。左侧缺少 ID 时,等值条件同样找不到匹配,因此该行会保留,除非显式要求左侧 ID 存在。
如果右侧空值表示无效数据,导入时就应验证或通过 NOT NULL 约束拒绝。保留 NOT IN 写法时,可以在右侧子查询加 IS NOT NULL,同时为左侧空值明确规则。验收至少包括右侧含空值、右侧为空集、左侧为空值以及实际命中四类样本。这里的结论针对筛选语义,性能需用实际索引和执行计划判断。
小结
先把过滤表达式作为一列查询,确认哪条记录变成了未知。按排除规则选择相关子查询和空值约束。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| SQLite SQL Language Expressions | https://www.sqlite.org/lang_expr.html#the_in_and_not_in_operators | NOT IN 真值表与 EXISTS 语义 |
| PostgreSQL 18 Subquery Expressions | https://www.postgresql.org/docs/18/functions-subquery.html#FUNCTIONS-SUBQUERY-NOTIN | NOT IN 的空值与子查询规则 |
核验说明
- Windows、Python 3.11.9、SQLite 3.45.1 验证三种过滤方式及返回行。PostgreSQL 部分依据对应版本文档。