IS NULL 与 NULL 判断(IS DISTINCT FROM)
NULL 不是「空字符串」,也不是「0」,而是「未知(unknown)」。它参与任何普通比较运算(=、<>、<、>)时结果都是 NULL,而不是 TRUE 或 FALSE。这决定了判断 NULL 必须使用专门的谓词:IS NULL、IS NOT NULL、IS DISTINCT FROM。本节系统梳理 PostgreSQL 的三值逻辑、NULL 安全比较、排序与聚合中的 NULL 行为,以及 NOT IN 遇 NULL 返回空集这一经典陷阱。
前置知识
阅读本节前,建议先了解:模式匹配(LIKE / SIMILAR TO / POSIX 正则)
基础概念
| 概念 | 术语 | 说明 |
|---|---|---|
| 三值逻辑 | Three-Valued Logic | 逻辑结果有三种:TRUE、FALSE、UNKNOWN(由 NULL 产生) |
| 空值 | NULL | 表示「未知」或「缺失」,不等同于空字符串 '' 或数值 0 |
| 空值传播 | NULL Propagation | 任何含 NULL 的普通算术/比较表达式,结果通常仍为 NULL |
| 空值判断谓词 | IS NULL / IS NOT NULL | 唯一可靠的 NULL 检测手段,永远返回 TRUE 或 FALSE |
| 空值安全比较 | NULL-Safe Comparison | IS DISTINCT FROM / IS NOT DISTINCT FROM,把 NULL 视为可比较的值 |
| 布尔断言 | IS TRUE / IS FALSE / IS UNKNOWN | PostgreSQL 特有,把三值逻辑结果收敛为两值布尔 |
| 空值排序 | NULLS FIRST / NULLS LAST | 显式控制 NULL 在 ORDER BY 中的位置 |
| 聚合忽略空值 | Aggregate Ignores NULL | sum、avg、max 等聚合函数自动跳过 NULL 行 |
| 唯一索引空值 | NULL in Unique Index | PostgreSQL 认为多个 NULL 互不相等,故唯一索引允许多行 NULL |
| 检查约束放行 | NULL in CHECK | CHECK 约束中条件结果为 UNKNOWN 时视为通过,即 NULL 被放行 |
理解 NULL 的关键在于记住一句话:NULL 代表「不知道」。既然不知道,那么「不知道 = 不知道」也无法判断真假,答案只能是「不知道」(UNKNOWN)。PostgreSQL 的一切 NULL 行为都从这个原则推导而来。
-- 三值逻辑的直观体现
SELECT
NULL = NULL AS "null_eq_null", -- NULL
1 = NULL AS "one_eq_null", -- NULL
NULL IS NULL AS "is_null", -- true
NULL IS NOT NULL AS "is_not_null", -- false
NULL IS DISTINCT FROM NULL AS "distinct"; -- false语法与用法
NULL 相关谓词一览:
| 表达式 | 返回类型 | 语义 |
|---|---|---|
expr IS NULL | boolean | 表达式为 NULL 时返回 TRUE |
expr IS NOT NULL | boolean | 表达式非 NULL 时返回 TRUE |
expr IS DISTINCT FROM v | boolean | 二者「不同」(NULL 视为一个确定值)时返回 TRUE |
expr IS NOT DISTINCT FROM v | boolean | 二者「相同」(含双方均为 NULL)时返回 TRUE |
expr IS TRUE | boolean | 表达式恰好为 TRUE(NULL 返回 FALSE) |
expr IS FALSE | boolean | 表达式恰好为 FALSE(NULL 返回 FALSE) |
expr IS UNKNOWN | boolean | 表达式为 NULL,即逻辑值为 UNKNOWN |
-- 最小可运行示例:在任何库中均可直接执行
SELECT
1 IS DISTINCT FROM NULL AS a, -- true:1 与 NULL 不同
NULL IS NOT DISTINCT FROM NULL AS b, -- true:双方都是 NULL,视为相同
(NULL = NULL) IS UNKNOWN AS c; -- true:普通比较结果为 UNKNOWN详细说明
三值逻辑与真值表
SQL 的逻辑不是二值的,而是三值的。AND、OR、NOT 在引入 NULL 后的完整真值表如下:
AND 真值表:
| AND | TRUE | FALSE | NULL |
|---|---|---|---|
| TRUE | TRUE | FALSE | NULL |
| FALSE | FALSE | FALSE | FALSE |
| NULL | NULL | FALSE | NULL |
OR 真值表:
| OR | TRUE | FALSE | NULL |
|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE |
| FALSE | TRUE | FALSE | NULL |
| NULL | TRUE | NULL | NULL |
规律很直观:
AND:只要有一边是FALSE,结果就是FALSE(FALSE具有「支配性」,能压过 NULL)。OR:只要有一边是TRUE,结果就是TRUE(TRUE具有支配性)。- 其余涉及 NULL 的情形结果都是 NULL。
SELECT
(TRUE AND NULL) AS "t_and_null", -- NULL
(FALSE AND NULL) AS "f_and_null", -- false
(TRUE OR NULL) AS "t_or_null", -- true
(FALSE OR NULL) AS "f_or_null", -- NULL
(NOT NULL) AS "not_null"; -- NULL这条规则解释了 WHERE 子句为何「丢掉」NULL 行:WHERE 只保留条件结果为 TRUE 的行,而结果为 FALSE 或 NULL(UNKNOWN)的行都会被过滤掉。
-- 假设 products.price 有 NULL 值
SELECT sku, price FROM products WHERE price > 100;
-- price 为 NULL 的行既不满足 TRUE 也不满足 FALSE,条件结果为 NULL,故被过滤IS NULL / IS NOT NULL
这是判断空值的标准写法,返回一个确定的布尔值,绝不会返回 NULL。
-- 找出所有价格未填写的商品
SELECT sku, name, price
FROM products
WHERE price IS NULL
ORDER BY sku;
-- 找出所有价格已填写的商品(NULL 行被排除)
SELECT sku, name, price
FROM products
WHERE price IS NOT NULL
ORDER BY sku;常见错误:用 = NULL 或 <> NULL 判断空值。
-- 错误!结果永远为空集,因为 price = NULL 恒为 NULL
SELECT sku FROM products WHERE price = NULL;
-- 正确
SELECT sku FROM products WHERE price IS NULL;同理,<> NULL、> NULL 等写法也全部是错误的,必须换成 IS NOT NULL / IS NULL。
IS DISTINCT FROM / IS NOT DISTINCT FROM
普通比较运算符在遇到 NULL 时结果不确定;IS DISTINCT FROM 系列则把 NULL 当作一个「可比较的确定值」来处理:两个 NULL 被视为相等。
其等价关系可以这样理解:
a IS DISTINCT FROM b⟺NOT (a IS NOT DISTINCT FROM b)a IS NOT DISTINCT FROM b⟺(a = b) IS NOT FALSE(即a = b为TRUE或NULL时都算「不区分」)
SELECT
1 IS DISTINCT FROM 2 AS a, -- true
1 IS DISTINCT FROM 1 AS b, -- false
1 IS DISTINCT FROM NULL AS c, -- true(普通 1 = NULL 会得 NULL)
NULL IS DISTINCT FROM NULL AS d; -- false(普通 NULL = NULL 会得 NULL)典型用途:字段的「变更检测」与「幂等 upsert」。当新旧值都可能为 NULL 时,用 = 无法正确判断,用 IS DISTINCT FROM 才能得到稳定结果。
-- 只更新真正发生变化的行(新旧值都可能为 NULL)
UPDATE products p
SET price = :new_price
WHERE p.sku = :sku
AND p.price IS DISTINCT FROM :new_price;IS TRUE / IS FALSE / IS UNKNOWN(PostgreSQL 特有)
PostgreSQL 提供了把三值逻辑「收敛」为两值布尔的谓词,常用于 CHECK 约束、部分索引、布尔列查询等场景。
| 谓词 | 布尔表达式为 TRUE | 为 FALSE | 为 NULL |
|---|---|---|---|
expr IS TRUE | TRUE | FALSE | FALSE |
expr IS FALSE | FALSE | TRUE | FALSE |
expr IS UNKNOWN | FALSE | FALSE | TRUE |
expr IS NOT TRUE | FALSE | TRUE | TRUE |
expr IS NOT FALSE | TRUE | FALSE | TRUE |
-- 只保留「明确为真」的行;NULL 行会被排除(与普通 WHERE 行为一致)
SELECT * FROM products WHERE (price > 100) IS TRUE;
-- 只保留「明确为假」的行;NULL 行会被排除
SELECT * FROM products WHERE (price > 100) IS FALSE;
-- 专门找出「无法判定」的行(price 为 NULL 导致条件为 UNKNOWN)
SELECT * FROM products WHERE (price > 100) IS UNKNOWN;= 与 IS DISTINCT FROM 的执行计划差异
IS DISTINCT FROM 因为要额外处理 NULL,无法直接套用普通 B-tree 索引的等值或范围扫描策略。对于 IS NOT DISTINCT FROM,PostgreSQL 可以把 NULL 也纳入索引范围(相当于 = 值 OR IS NULL),但代价是可能退化为 Bitmap 扫描。
-- 普通等值:可以走索引
EXPLAIN (ANALYZE, COSTS OFF)
SELECT * FROM products WHERE sku = 'SKU-1001';
-- 空值安全比较:可能走 Bitmap 扫描(含 NULL 分支)
EXPLAIN (ANALYZE, COSTS OFF)
SELECT * FROM products WHERE sku IS NOT DISTINCT FROM 'SKU-1001';在数据量大、且确定列上没有 NULL 的情况下,用普通 = 更利于优化器生成高效计划;只有确实需要处理 NULL 时才使用 IS NOT DISTINCT FROM。
NULL 在 CHECK 约束中的放行行为
CHECK 约束的判定规则是:只有条件结果为 FALSE 时才拒绝写入;结果为 TRUE 或 NULL(UNKNOWN)都视为通过。这意味着一旦条件里出现 NULL,约束就形同虚设。
-- 危险!price 为 NULL 时条件为 NULL,约束不生效
CREATE TABLE products_bad (
sku text PRIMARY KEY,
price numeric CHECK (price > 0)
);
-- 插入 price 为 NULL 的行会成功,绕过约束
INSERT INTO products_bad (sku, price) VALUES ('SKU-9001', NULL); -- 成功!
-- 正确:显式加上 NOT NULL 或把 NULL 也视为非法
CREATE TABLE products_ok (
sku text PRIMARY KEY,
price numeric NOT NULL CHECK (price > 0)
);如果要允许 NULL,但又想校验「非 NULL 时必须满足条件」,可写成 price IS NULL OR price > 0。
NULL 在唯一索引中的行为
SQL 标准规定:每个 NULL 都与其它 NULL「不相等」,因此唯一索引允许多行 NULL。PostgreSQL 严格遵守这一行为。
CREATE TABLE users (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text UNIQUE, -- email 可为 NULL,且允许多行 NULL
phone text
);
-- 以下多行 email 为 NULL 都会插入成功
INSERT INTO users (email) VALUES (NULL), (NULL), (NULL); -- 3 行均成功
-- 非 NULL 的重复值则会被拒绝
INSERT INTO users (email) VALUES ('a@example.com');
INSERT INTO users (email) VALUES ('a@example.com'); -- 错误!违反唯一约束如果业务上希望「NULL 也只能出现一次」,可以用部分唯一索引配合表达式:
-- PostgreSQL 15+ 起可直接在唯一约束上声明 NULLS NOT DISTINCT
CREATE TABLE users_strict (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text UNIQUE NULLS NOT DISTINCT -- 多个 NULL 也会被视为重复
);NULL 排序:NULLS FIRST / NULLS LAST
PostgreSQL 默认认为 NULL 是「最大」的值:
ORDER BY col ASC时 NULL 排在最后;ORDER BY col DESC时 NULL 排在最前。
用 NULLS FIRST / NULLS LAST 可以显式覆盖默认行为。
-- 默认 ASC:NULL 在最后
SELECT sku, price FROM products ORDER BY price ASC;
-- 显式让 NULL 排最前
SELECT sku, price FROM products ORDER BY price ASC NULLS FIRST;
-- 想让 NULL 排最后(无论升降序)
SELECT sku, price FROM products ORDER BY price DESC NULLS LAST;NULLS FIRST / NULLS LAST 也可以用在本节介绍的 IS NULL 排序技巧上,或配合索引使用:
-- 让「已填写价格」的商品优先展示
SELECT sku, price FROM products ORDER BY (price IS NULL), price;聚合函数忽略 NULL
除 count(*) 外,所有标准聚合函数(sum、avg、min、max、array_agg 等)都会跳过 NULL 行,这一点极易与 count(*) 混淆。
| 写法 | 语义 |
|---|---|
count(*) | 统计所有行数(含全 NULL 行) |
count(col) | 统计 col 非 NULL 的行数 |
count(DISTINCT col) | 统计 col 去重后的非 NULL 值个数 |
sum(col) | 忽略 NULL 求和;若全为 NULL 则返回 NULL(不是 0) |
avg(col) | 忽略 NULL 求平均,分母是非 NULL 的个数 |
SELECT
count(*) AS total_rows, -- 所有行
count(price) AS priced_rows, -- price 非 NULL 的行
sum(price) AS total_price, -- 忽略 NULL
avg(price) AS avg_price, -- 分母为非 NULL 行数
coalesce(sum(price), 0) AS total_or_zero; -- 全 NULL 时兜底为 0NOT IN 遇 NULL 返回空集的经典陷阱
这是 SQL 中最著名的坑之一。NOT IN (子查询) 在子查询结果含 NULL 时,会整体返回空集,因为 x <> NULL 恒为 NULL,AND 串起来后整条条件无法为 TRUE。
-- 假设 orders.product_id 中有 NULL
-- 陷阱:只要子查询里出现一个 NULL,结果就变成空集
SELECT * FROM products
WHERE id NOT IN (SELECT product_id FROM orders); -- 可能返回 0 行!
-- 方案 A:过滤掉子查询中的 NULL
SELECT * FROM products
WHERE id NOT IN (SELECT product_id FROM orders WHERE product_id IS NOT NULL);
-- 方案 B:改用 NOT EXISTS(推荐,语义清晰且天然处理 NULL)
SELECT * FROM products p
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.product_id = p.id
);IN(正匹配)遇到 NULL 时通常不受影响:x IN (1, NULL) 只要 x = 1 就为 TRUE,只有 x 不在列表中且列表含 NULL 时结果才是 NULL,最终同样会被 WHERE 滤掉。
COALESCE 兜底与 NULL 的转换
用 coalesce() 可以把 NULL 替换为默认值,从而「消解」NULL 带来的三值逻辑问题。注意它只是查询层的兜底,并不改变底层数据。
SELECT
sku,
coalesce(price, 0) AS price_or_zero,
coalesce(description, '暂无描述') AS description_safe
FROM products;
-- 排序时把 NULL 当作一个极小值处理
SELECT sku, coalesce(price, -1) AS price_key FROM products ORDER BY price_key;空字符串不是 NULL
从别的系统或语言迁移过来时,最容易把二者混淆。PostgreSQL 中 '' 是一个确定的值,'' IS NULL 为 FALSE。
SELECT
'' IS NULL AS empty_is_null, -- false
'' = '' AS empty_eq_empty, -- true
length('') AS empty_length, -- 0
nullif('', '') IS NULL AS nullif_demo; -- true(nullif 把相等值转成 NULL)在数据清洗中,常需要把「空字符串」统一为 NULL:
UPDATE products
SET description = NULL
WHERE description = ''; -- 空串归一为 NULL实战示例
步骤 1:建表并造数据
-- 统一的示例库
CREATE DATABASE demo;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS products;
CREATE TABLE products (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
sku text NOT NULL UNIQUE,
name text NOT NULL,
price numeric(10, 2), -- 允许 NULL,表示价格待定
description text, -- 允许 NULL
released_on date -- 允许 NULL,表示未上架
);
CREATE TABLE orders (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_no text NOT NULL,
product_id integer, -- 允许 NULL,表示已删除商品
qty integer,
amount numeric(10, 2),
note text
);
INSERT INTO products (sku, name, price, description, released_on) VALUES
('SKU-1001', '27 英寸显示器', 1299.00, '2K 分辨率', '2025-01-10'),
('SKU-1002', '机械键盘', 399.00, '青轴', '2025-02-01'),
('SKU-1003', '无线鼠标', 159.00, NULL, '2025-02-15'),
('SKU-1004', 'HDMI 线缆', NULL, '2 米长', '2025-03-01'),
('SKU-1005', '显卡支架', NULL, NULL, NULL),
('SKU-1006', 'USB 集线器', 89.00, '7 口', '2025-03-20');
INSERT INTO orders (order_no, product_id, qty, amount, note) VALUES
('SO-2001', 1, 2, 2598.00, NULL),
('SO-2002', 2, 1, 399.00, '加急'),
('SO-2003', NULL, 1, 159.00, NULL), -- 商品已被删除
('SO-2004', 3, 3, 477.00, NULL),
('SO-2005', NULL, 2, 178.00, '补发');步骤 2:三值逻辑与 IS NULL 基础查询
-- 找出价格未定的商品
SELECT sku, name, price
FROM products
WHERE price IS NULL
ORDER BY sku; sku | name | price
--------+------------+-------
SKU-1004 | HDMI 线缆 |
SKU-1005 | 显卡支架 |
(2 rows)-- 统计「有价格」与「无价格」的商品数量
SELECT
count(*) AS total,
count(price) AS priced,
count(*) - count(price) AS unpriced
FROM products; total | priced | unpriced
-------+--------+----------
6 | 4 | 2
(1 row)步骤 3:IS DISTINCT FROM 做变更检测
-- 模拟一次价格更新:只有真正变化的行才会被更新
-- 对 SKU-1004 而言,原值为 NULL,新值 299.00,属于「不同」,需更新
BEGIN;
UPDATE products
SET price = 299.00
WHERE sku = 'SKU-1004'
AND price IS DISTINCT FROM 299.00;
SELECT sku, price FROM products WHERE sku = 'SKU-1004';
ROLLBACK; -- 仅演示,回滚以保持数据一致 sku | price
---------+--------
SKU-1004 | 299.00
(1 row)对比用普通 = 的后果:当原值为 NULL 时,price <> 299.00 结果为 NULL,条件不成立,该行会被静默跳过,导致更新丢失。
步骤 4:NOT IN 陷阱与 NOT EXISTS 修复
-- 陷阱:orders.product_id 含 NULL,NOT IN 会让整个查询返回空集
SELECT id, sku, name
FROM products
WHERE id NOT IN (SELECT product_id FROM orders); id | sku | name
----+-----+------
(0 rows)-- 修复方案 A:在子查询中排除 NULL
SELECT id, sku, name
FROM products
WHERE id NOT IN (SELECT product_id FROM orders WHERE product_id IS NOT NULL)
ORDER BY id; id | sku | name
----+----------+--------------
4 | SKU-1004 | HDMI 线缆
5 | SKU-1005 | 显卡支架
6 | SKU-1006 | USB 集线器
(3 rows)-- 修复方案 B(推荐):NOT EXISTS
SELECT p.id, p.sku, p.name
FROM products p
WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.product_id = p.id
)
ORDER BY p.id; id | sku | name
----+----------+--------------
4 | SKU-1004 | HDMI 线缆
5 | SKU-1005 | 显卡支架
6 | SKU-1006 | USB 集线器
(3 rows)步骤 5:聚合中的 NULL 与排序控制
-- 聚合忽略 NULL:sum 与 avg 都跳过 NULL,count(*) 不跳
SELECT
count(*) AS order_rows,
count(product_id) AS with_product,
count(note) AS with_note,
sum(amount) AS total_amount,
round(avg(amount), 2) AS avg_amount
FROM orders; order_rows | with_product | with_note | total_amount | avg_amount
------------+--------------+-----------+--------------+------------
5 | 3 | 2 | 3810.00 | 762.00
(1 row)-- 排序:NULL 默认在 ASC 时排最后
SELECT order_no, note FROM orders ORDER BY note ASC; order_no | note
----------+------
SO-2002 | 加急
SO-2005 | 补发
SO-2001 |
SO-2003 |
SO-2004 |
(5 rows)-- 显式让 NULL 排最前
SELECT order_no, note FROM orders ORDER BY note ASC NULLS FIRST; order_no | note
----------+------
SO-2001 |
SO-2003 |
SO-2004 |
SO-2002 | 加急
SO-2005 | 补发
(5 rows)步骤 6:NULL 与唯一索引、CHECK 约束
-- 唯一索引允许多行 NULL
INSERT INTO products (sku, name, price, description, released_on)
VALUES
('SKU-1007', '散热硅脂', NULL, NULL, NULL),
('SKU-1008', '理线带', NULL, NULL, NULL);
SELECT count(*) AS null_price_rows FROM products WHERE price IS NULL; null_price_rows
-----------------
4
(1 row)-- CHECK 约束对 NULL 放行(危险示例)
CREATE TABLE demo_check_bad (
sku text,
price numeric CHECK (price > 0)
);
INSERT INTO demo_check_bad (sku, price) VALUES ('X-1', NULL) RETURNING sku, price; sku | price
-----+-------
X-1 |
(1 row)注意事项
- 不要用
= NULL/<> NULL判断空值。 结果恒为NULL,条件不成立。必须用IS NULL/IS NOT NULL。
-- 错误!
SELECT * FROM products WHERE price = NULL;NOT IN (子查询)前务必确认子查询无 NULL。 存在 NULL 时整个查询返回空集,是线上事故高发点。
-- 不推荐
SELECT * FROM products WHERE id NOT IN (SELECT product_id FROM orders);
-- 推荐
SELECT * FROM products WHERE NOT EXISTS (
SELECT 1 FROM orders o WHERE o.product_id = products.id
);CHECK约束遇 NULL 会放行。 想让约束真正生效,需配合NOT NULL,或把 NULL 显式纳入判断。
-- 不推荐:NULL 可绕过
price numeric CHECK (price > 0)
-- 推荐:显式处理 NULL
price numeric CHECK (price IS NULL OR price > 0)count(*)与count(col)结果通常不同。 前者统计行数,后者只统计非 NULL 值,混用会导致口径错误。sum/avg在全为 NULL 时返回 NULL,而不是 0。 需要 0 时必须用coalesce(sum(col), 0)。avg的分母是非 NULL 行数,不是总行数。用 NULL 表示「缺失」时,这个行为可能与你期望的「按全体行数平均」不一致。IS TRUE/IS FALSE会把 NULL 归入FALSE。 若要保留 NULL 行,应改用IS NOT FALSE或IS UNKNOWN。IS DISTINCT FROM会削弱索引可用性。 能确定无 NULL 时优先用普通=,避免优化器退化为顺序扫描。
最佳实践
- 字段语义为「必填」时一律加
NOT NULL,把空值问题在建模阶段消灭掉,这比在查询层反复兜底划算得多。
CREATE TABLE orders (
order_no text NOT NULL,
amount numeric NOT NULL DEFAULT 0
);区分「未知」与「不适用」。若 NULL 表达的是「不适用」,考虑用明确的枚举值或独立布尔列代替,避免三值逻辑带来的歧义。
反连接优先用
NOT EXISTS,它天然正确处理 NULL,且语义比NOT IN更贴合「不存在匹配行」的业务含义。
SELECT p.* FROM products p
WHERE NOT EXISTS (SELECT 1 FROM order_items i WHERE i.product_id = p.id);- 变更检测用
IS DISTINCT FROM,避免新旧值为 NULL 时更新被静默跳过。
UPDATE products SET price = :new
WHERE sku = :sku AND price IS DISTINCT FROM :new;- 聚合结果统一用
coalesce兜底,防止前端拿到 NULL 后渲染异常。
SELECT sku, coalesce(sum(amount), 0) AS total FROM orders GROUP BY sku;- 排序涉及可空列时显式声明
NULLS FIRST/NULLS LAST,不要依赖默认行为,以免升级或改写 SQL 后顺序变化。
SELECT * FROM products ORDER BY released_on DESC NULLS LAST;- 数据清洗时把空字符串统一归一为 NULL,并在应用层同时约定「只写 NULL,不写空串」。
UPDATE products SET description = NULL WHERE description = '';在
CHECK约束里显式处理 NULL,或直接把列声明为NOT NULL,避免约束被 NULL 绕过。用
IS UNKNOWN调试三值逻辑问题。当查询结果「数量不对」时,先怀疑某个条件因为 NULL 变成了UNKNOWN。
-- 定位因 NULL 而无判定结果的行
SELECT * FROM products WHERE (price > 100) IS UNKNOWN;下一节