KernelRanch
返回文章列表

一条 NULL 为什么让 NOT IN 查不到任何行

2026-11-13 #sql#sqlite#null

一份排除名单混入 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 Expressionshttps://www.sqlite.org/lang_expr.html#the_in_and_not_in_operatorsNOT IN 真值表与 EXISTS 语义
PostgreSQL 18 Subquery Expressionshttps://www.postgresql.org/docs/18/functions-subquery.html#FUNCTIONS-SUBQUERY-NOTINNOT IN 的空值与子查询规则

核验说明

  • Windows、Python 3.11.9、SQLite 3.45.1 验证三种过滤方式及返回行。PostgreSQL 部分依据对应版本文档。