在数据库查询中,筛选空值数据是一个高频操作,但很多开发者,尤其是初学者,常常会在这里踩坑。一个最典型的错误就是试图用 = NULL 来查找空值,结果发现永远查不到数据。
记住这个核心结论:判断空值,请用 IS NULL,而不是 = NULL。后者在任何情况下都不会返回你想要的结果。

为什么 = NULL 一定失效
问题的根源在于对 NULL 的理解。NULL 在 SQL 中并非一个具体的“值”,而是代表“未知”或“缺失”的特殊标记。因此,任何与 NULL 进行的等值比较(包括 =、!= 或 <>),其结果都不是 TRUE 或 FALSE,而是第三个逻辑值:UNKNOWN。
而 SQL 的 WHERE 子句有一个基本原则:它只保留条件计算结果为 TRUE 的行。无论是 FALSE 还是 UNKNOWN,都会被过滤掉。这就导致了以下常见现象:
- 当你写下
WHERE email = NULL时,即使表中所有email字段都是NULL,查询结果也永远是空的。 - 更隐蔽的陷阱是,写
WHERE status != 'done'时,那些status为NULL的记录也会被漏掉,因为NULL != 'done'的结果同样是UNKNOWN,不会被纳入结果集。
IS NULL 和 IS NOT NULL 的基本用法
为了解决这个问题,SQL 标准专门引入了 IS NULL 和 IS NOT NULL 操作符。这是唯一正确且跨数据库(如 MySQL、PostgreSQL、SQL Server、Oracle)通用的空值判断方式。
具体怎么用?看几个例子就明白了:
- 查找空值记录:
SELECT * FROM users WHERE email IS NULL - 查找非空值记录:
SELECT * FROM orders WHERE logistics_no IS NOT NULL - 多字段同时非空:
WHERE name IS NOT NULL AND phone IS NOT NULL
这里有个关键点需要注意:IS NOT NULL 只判断字段是否为 NULL。如果字段里存的是空字符串 '' 或者纯空格 ' ',它会被认为是“非空”的。这一点常常是业务逻辑混淆的开始。
业务上常说的“空”往往不止 NULL
在实际业务场景中,我们所说的“空”或“未填写”,其含义往往比单纯的 NULL 更广。例如,一个用户手机号字段,数据库里可能同时存在三种情况:真正的 NULL(未录入)、空字符串 ''(用户提交了空表单)、以及包含空格的字符串 ' '(前端未做修剪)。在业务逻辑上,这三者通常都需要被视为“无效数据”或“未填写”。
因此,更严谨的过滤方式应该是:
- 排除 NULL 和空字符串:
WHERE phone IS NOT NULL AND phone != '' - 进一步排除空白字符(适用于 MySQL/PostgreSQL):
WHERE phone IS NOT NULL AND TRIM(phone) != '' - 在 SQL Server 中,修剪函数略有不同:
WHERE phone IS NOT NULL AND RTRIM(LTRIM(phone)) != '' - SQLite 的
TRIM()函数功能相对基础,但用于去除首尾空格通常是足够的。
容易忽略的性能和索引影响
语法正确只是第一步,在数据量大的场景下,性能考量同样重要。IS NULL 条件本身是可以利用索引的,但其效果受数据分布和索引结构的影响很大。
有几个关键点需要了解:
- 数据分布影响:如果一个字段中
NULL值的比例极高(例如超过95%),数据库优化器可能会判断使用索引不如直接进行全表扫描更高效,从而放弃使用索引。 - 复合索引的限制:对于复合索引
(a, b),查询条件WHERE a IS NULL AND b = 1通常无法有效利用该索引。因为索引的排序结构在遇到NULL时变得复杂。 - 优化策略:如果针对空值的查询非常频繁且是性能瓶颈,可以考虑创建函数索引(例如在 PostgreSQL 中:
CREATE INDEX ON t ((col IS NULL))),或者在应用层设计时,就增加一个冗余的布尔标记字段来标识该字段是否为空,并对这个标记字段建立索引。
说到底,技术上的语法选择并不复杂。真正的挑战在于业务逻辑的厘清:你口中的这个“空”,到底指的是什么?是用户没填?是填了又被删除了?还是系统流程尚未走到生成该字段的那一步?不同的含义对应着不同的数据过滤组合。想不清楚这一点,就很容易写出要么漏数据、要么误筛数据的查询语句。
