PostgreSQL中NULL陷阱的排除过滤指南

 更新时间:2026年05月17日 14:49:12   作者:倒流时光三十年  
文章讲述了在SQL中处理NULL值时遇到的常见问题,尤其在使用三值逻辑时容易出现问题,通过具体例子展示了如何避免隐式过滤NULL值,并提出了清晰的解决方案,同时,还列举了其他常见的NULL值陷阱,并提供了快速验证字段NULL情况的方法,需要的朋友可以参考下

一、背景:看似简单的需求

在一次数据集成任务中,遇到了这样一个业务过滤需求:

当销售区域为"拉美(LA)"时,需要排除 region = 'BR' 并且 order_status = 'CANCELED' 的订单数据。但有一个特别要求:如果这两个字段中任意一个为 NULL,该行数据必须保留。

需求很清晰,但实际写 SQL 时,才发现这里面藏着一个很经典的坑——SQL 的 NULL 三值逻辑

二、最初的写法及其隐患

直觉上,"排除某个组合"会写成:

AND NOT (region = 'BR' AND order_status = 'CANCELED')

这段 SQL 在大多数场景下跑起来是"正确"的,但它实际上依赖了 SQL NULL 三值逻辑的一个"副作用"来保留 NULL 行——这是隐式的,而非明确表达的意图,属于代码意图不清晰的隐患

三、SQL 的三值逻辑(Three-Valued Logic)

这是理解 NULL 问题的基础。

和编程语言中的布尔两值逻辑(true / false)不同,SQL 采用的是三值逻辑

含义
TRUE条件成立
FALSE条件不成立
UNKNOWN不确定(NULL 参与运算的结果)

核心规则:WHERE 子句只保留结果为 TRUE 的行,UNKNOWNFALSE 都会被过滤。

NULL 参与任何比较运算,结果几乎都是 UNKNOWN

NULL = 'CANCELED'    → UNKNOWN
NULL != 'CANCELED'   → UNKNOWN
NULL AND TRUE        → UNKNOWN
NOT NULL             → UNKNOWN

四、NOT (A AND B)遇到 NULL 时的完整分析

还原本文场景,逐行分析:

AND NOT (region = 'BR' AND order_status = 'CANCELED')
regionorder_statusA='BR'B='CANCELED'A AND BNOT(A AND B)WHERE 结果
'BR''CANCELED'TRUETRUETRUEFALSE❌ 被排除
'BR''OTHER'TRUEFALSEFALSETRUE✅ 保留
'US''CANCELED'FALSETRUEFALSETRUE✅ 保留
NULL'CANCELED'UNKNOWNTRUEUNKNOWNUNKNOWN⚠️ 被过滤!
'BR'NULLTRUEUNKNOWNUNKNOWNUNKNOWN⚠️ 被过滤!
NULLNULLUNKNOWNUNKNOWNUNKNOWNUNKNOWN⚠️ 被过滤!

结论:NOT (A AND B) 无法保留 NULL!NULL 行因结果是 UNKNOWN 而被悄悄过滤。

五、为什么"跑起来没报错"就以为是对的?

这正是最危险的地方。

如果在数据质量较好的表中,region 字段实际上从不出现 NULL(比如它来自一个外键关联,能关联上的必然有值),那这段 SQL 跑起来结果看上去完全正确。

但一旦:

  • 上游数据质量下降,出现 NULL
  • 表结构调整,字段变为可空
  • 换了一张数据较"脏"的表

原本"正确"的 SQL 就会悄无声息地少数据,排查起来极其困难。

六、正确写法:显式声明 NULL 保留

AND (region IS NULL OR region != 'BR' OR order_status IS NULL OR order_status != 'CANCELED')

逻辑含义:满足以下任意一个条件就保留这行数据:

  1. region 是 NULL
  2. region 不等于 'BR'
  3. order_status 是 NULL
  4. order_status 不等于 'CANCELED'

唯一被排除的,是同时满足:region = 'BR' order_status = 'CANCELED'(且两者都不为 NULL)。

七、三种写法对比

写法region=NULL 时order_status=NULL 时意图清晰度推荐
NOT (A AND B)⚠️ 隐式过滤⚠️ 隐式过滤❌ 差
A IS NULL OR A!='BR' OR B IS NULL OR B!='CANCELED'✅ 明确保留✅ 明确保留✅ 好
NOT (A='BR' AND B='CANCELED' AND A IS NOT NULL AND B IS NOT NULL)✅ 明确保留✅ 明确保留一般可接受

八、延伸:其他高频 NULL 陷阱

陷阱 1:!=不等于不能过滤 NULL

-- ❌ 错误:order_status 是 NULL 的行也会被过滤掉
WHERE order_status != 'CANCELED'

-- ✅ 正确:明确保留 NULL
WHERE order_status != 'CANCELED' OR order_status IS NULL

陷阱 2:NOT IN遇到子查询有 NULL,全部结果为空

-- ❌ 危险:子查询结果中有一个 NULL,整个查询返回空!
WHERE order_id NOT IN (SELECT order_id FROM blacklist_orders)

-- ✅ 安全写法:过滤子查询中的 NULL
WHERE order_id NOT IN (
    SELECT order_id FROM blacklist_orders WHERE order_id IS NOT NULL
)

-- ✅ 更推荐:用 NOT EXISTS,天然不受 NULL 影响
WHERE NOT EXISTS (
    SELECT 1 FROM blacklist_orders WHERE blacklist_orders.order_id = t.order_id
)

陷阱 3:聚合函数中的 NULL

COUNT(*)           -- 统计所有行,NULL 也计入
COUNT(order_status) -- 忽略 NULL 行,两者结果可能不同!

SUM(amount)        -- NULL 行被忽略,不是当 0 处理
AVG(amount)        -- 分母只统计非 NULL 行,结果可能偏高

九、快速验证字段是否有 NULL

在写过滤条件之前,先查一下字段的 NULL 情况,是一个好习惯:

-- 检查字段 NULL 数量
SELECT
    COUNT(*) AS total,
    COUNT(region) AS region_not_null,
    COUNT(*) - COUNT(region) AS region_null_count,
    COUNT(order_status) AS status_not_null,
    COUNT(*) - COUNT(order_status) AS status_null_count
FROM orders
WHERE geo = 'LA';

十、总结:黄金法则

凡是业务上需要"保留 NULL"或"排除 NULL"的场景,必须用 IS NULL / IS NOT NULL 显式处理,绝不能依赖三值逻辑的副作用。

记住这三句话:

✅ 显式优于隐式 —— 意图要写清楚,不要靠"副作用"
✅ 先查 NULL 分布 —— 动手写条件前,先确认字段是否可空
✅ UNKNOWN ≠ FALSE —— NULL 参与运算结果是 UNKNOWN,WHERE 会过滤它

附:本文最终落地的 SQL 写法(MyBatis XML)

<!-- 只在 geo = LA 时追加此过滤条件 -->
<if test="geo == 'LA'">
    AND (region IS NULL OR region != 'BR'
         OR order_status IS NULL OR order_status != 'CANCELED')
</if>

读法:明确排除"region 确实等于 BR 且 order_status 确实等于 CANCELED"的行,其余所有行(包括任意字段为 NULL 的行)一律保留。意图清晰,无歧义,无副作用依赖。

番外:用 COALESCE 能解决吗? 能,比如这样:

 <if test="geo == 'LA'">
    and not (COALESCE(region_cd,'') = 'BR' AND COALESCE (order_status,'') = 'CREDIT NOTE')
</if>

为什么本文没有选择 COALESCE?

虽然 COALESCE 可行,但在本场景中有几个明显缺点:

❌ 缺点 1:占位符存在歧义风险

缺点 2:索引失效,影响查询性能

❌ 缺点 3:可读性可能不太好

反正合适就好吧!

以上就是PostgreSQL中NULL陷阱的排除过滤指南的详细内容,更多关于PostgreSQL NULL陷阱排除过滤的资料请关注脚本之家其它相关文章!

相关文章

  • PostgreSQL 自动Vacuum配置方式

    PostgreSQL 自动Vacuum配置方式

    这篇文章主要介绍了PostgreSQL 自动Vacuum配置方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • PostgreSQL执行计划的使用与查看教程

    PostgreSQL执行计划的使用与查看教程

    PostgreSQL中的执行计划(或查询计划)是数据库管理系统用来详细说明如何执行特定SQL查询的一系列操作步骤,简单来说,执行计划就是数据库如何解读你的SQL语句,本文给大家介绍了PostgreSQL执行计划的使用与查看教程,需要的朋友可以参考下
    2025-09-09
  • 解决PostgreSQL日志信息占用磁盘过大的问题

    解决PostgreSQL日志信息占用磁盘过大的问题

    解决PostgreSQL日志信息占用磁盘过大的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • Postgresql 存储过程(plpgsql)两层for循环的操作

    Postgresql 存储过程(plpgsql)两层for循环的操作

    这篇文章主要介绍了Postgresql 存储过程(plpgsql)两层for循环的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • PostgreSQL之分区表(partitioning)

    PostgreSQL之分区表(partitioning)

    通过合理的设计,可以将选择一定的规则,将大表切分多个不重不漏的子表,这就是传说中的partitioning。比如,我们可以按时间切分,每天一张子表,比如我们可以按照某其他字段分割,总之了就是化整为零,提高查询的效能
    2016-11-11
  • postgresql 实现查询某时间区间的所有日期案例

    postgresql 实现查询某时间区间的所有日期案例

    这篇文章主要介绍了postgresql 实现查询某时间区间的所有日期案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-01-01
  • RuoYi从MySQL迁移到PostgreSQL数据库的踩坑实录

    RuoYi从MySQL迁移到PostgreSQL数据库的踩坑实录

    随着业务发展对数据库性能要求的提升,MySQL迁移至PostgreSQL成为企业级应用的常见需求,下面这篇文章主要介绍了RuoYi从MySQL迁移到PostgreSQL数据库的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2026-02-02
  • postgres 数据库迁移的几种方法

    postgres 数据库迁移的几种方法

    本文详细介绍了postgres 数据库迁移的几种方法,包括数据文件备份、数据文件迁移方案以及常见问题及其解决方案,感兴趣的可以了解一下
    2025-07-07
  • PostgreSQL实现交叉表(行列转换)的5种方法示例

    PostgreSQL实现交叉表(行列转换)的5种方法示例

    这篇文章主要给大家介绍了关于PostgreSQL实现交叉表(行列转换)的5种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-08-08
  • PostgreSQL数据库字符串拼接、大小写转换以及substring详解

    PostgreSQL数据库字符串拼接、大小写转换以及substring详解

    在日常工作中会遇到将多行的值拼接为一个值展现,下面这篇文章主要给大家介绍了关于PostgreSQL数据库字符串拼接、大小写转换以及substring的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-04-04

最新评论