PostgreSQL 15: 唯一约束中对空值 NULL 的处理

John Doe 八月 11, 2025

你需要防止在唯一索引中插入带有空值 NULL 的多条记录吗?现在,PostgreSQL 添加了唯一约束的 NULL 处理选项。

非洲大草原上的一头大象

特性提交日志

添加唯一约束的 NULL 处理选项。

SQL 标准对于唯一约束中的 NULL 值应被视为相等还是不相等一直存在歧义。不同的数据库实现有着不同的行为。在 SQL:202x 草案中,这一问题得到了规范化:将其定义为依赖具体实现,并在唯一约束定义中添加了 UNIQUE [ NULLS [NOT] DISTINCT ] 选项,允许显式选择行为。

本次提交为 PostgreSQL 新增了这一选项。默认行为仍为UNIQUE NULLS DISTINCT(即将 NULL 视为不同)。在 B 树索引中实现这一点相当简单;提交的大部分内容只是将标识传递到所有需要它的地方。

CREATE UNIQUE INDEX的语法扩展并非来自 SQL 标准,而是 PostgreSQL 特有的。

讨论https://www.postgresql.org/message-id/flat/84e5ee1b-387e-9a54-c326-9082674bde78@enterprisedb.com

示例

PostgreSQL 新版本添加了 UNIQUE 空值处理选项。这是什么功能呢?让我们来通过一个小示例,展示下这项新功能的效果。

考虑以下表和唯一索引:

create table t ( a int );
create unique index i on t ( a );

显然,这会阻止我们多次添加相同的值:

insert into t values (1);

insert into t values (1);
ERROR:  duplicate key value violates unique constraint "i"
DETAIL:  Key (a)=(1) already exists.

但是对于 NULL 呢?NULL 表示未定义,那么当你尝试插入多行 NULL 值时,唯一索引应该如何处理呢?两个 NULL 值是相同的还是不同的?如果你问 PostgreSQL NULL 是否等于 NULL,得到的结果是未定义(或 NULL):

select null = null;
 ?column?
----------

(1 row)

将未定义的东西与另一个未定义的东西进行比较没有多大意义。我们上面创建的唯一索引的行为正是如此:

insert into t values(null);
insert into t values(null);
insert into t values(null);
insert into t values(null);
insert into t values(null);

PostgreSQL 新版本会为你提供选择:

create table tt ( a int );
create unique index ii on tt (a) nulls not distinct;

insert into tt values(null);

insert into tt values(null);
ERROR:  duplicate key value violates unique constraint "ii"
DETAIL:  Key (a)=(null) already exists.

当然,默认行为与我们现在的行为一致,但也可以显式指定:

create unique index iii on tt (a) nulls distinct;

非常不错的体验。感谢社区的所有相关人员。

参考

提交日志:https://git.postgresql.org/pg/commitdiff/94aa7cc5f707712f592885995a28e018c7c80488