我正在 PostgreSQL v14.9 中创建一个表,该表对两列有唯一约束:A 和 B,其中 B 可以为 null。由于 PostgreSQL 不会将值的缺失视为相等 (NULL),因此它将允许具有相同 A 值且 B 设置为 null 的行。
行:(“mydata”,空) 行:(“mydata”,空)
这应该被约束捕获,并且可以通过使用部分索引而不是创建两个索引来实现。一种类似于常规索引,每当 B 有值时,另一种使用 where 子句来检查 B 是否为空。
如果我使用这两个索引,它就会按预期工作。上述行将不允许进入数据库,但是我无法在不破坏约束的情况下更新行,因为即使使用事务性,PostgreSQL也会在提交之前的每次更新后进行评估。解决方法是在定义唯一约束时设置可延迟的初始延迟模式。但这不适用于索引,并且唯一约束不允许使用 where 子句。
我正在针对 PostgreSQL 数据库使用 Spring Data JPA 和 Hibernate,因此我无法编写 PostgreSQL 特定的 SQL 查询,这意味着我必须使我的初始迁移脚本设置一切正确。
这是我的数据库表定义: 但这将允许“已删除”为空值。
create table myTable
(
some_key uuid not null,
name VARCHAR(255) not null,
deleted TIMESTAMP,
primary key (some_key),
constraint myConstraint unique (name, deleted)
);
因此,为了修复空值,我会删除唯一约束,并像这样创建两个索引。 但是,如果我有这样的两行。 第 1 行:(“mydata”,空) 第 2 行:(“mydata”,someDate) 我希望切换它们,以便 Row1 被删除,Row2 变为空,当我进行更新时,我会遇到约束冲突。如前所述,PostgreSQL 将在每次更新后进行检查,而不是在执行提交时进行检查。
create table myTable
(
some_key uuid not null,
name VARCHAR(255) not null,
deleted TIMESTAMP,
primary key (some_key)
);
create unique index idx_one ON myTable (name, deleted);
create unique index idx_two ON myTable (name) where deleted is null;
要更改检查约束的时间,我可以使用可延迟初始延迟模式,但以下约束不会将空值视为相等。
create table myTable
(
some_key uuid not null,
name VARCHAR(255) not null,
deleted TIMESTAMP,
primary key (some_key),
constraint myConstraint unique (name, deleted) deferrable initially deferred
);
在我的约束中使用 where 子句(如下所示)是错误的语法,而且是不可能的。
create table myTable
(
some_key uuid not null,
name VARCHAR(255) not null,
deleted TIMESTAMP,
primary key (some_key),
constraint myConstraint unique (name, deleted) deferrable initially deferred,
constraint myConstraint unique (name) where deleted is null
);
所以我要求的是跨两列的唯一约束,其中一列可能为空,这应该被认为是相等的,并且使我可以更新多行,并且仅在整个更新完成后才检查约束。 Transactional 在 PostgreSQL 中不能确保这一点,我使用的是 v14.9,所以作为旁注,我不能使用 DISTINCT NOT NULL 来绕过部分索引,因为这仅在 v15+ 中引入。
如有任何帮助,我们将不胜感激。
三种可能的解决方案:
这是最好的,也是你不想听的:
升级到 PostgreSQL v16 并将约束定义为
UNIQUE NULLS NOT DISTINCT
。
不要为未删除的行存储 NULL,而是存储
infinity
。deleted
声明为 NOT NULL
并创建一个正常的唯一约束。
不要创建约束,而是创建唯一索引:
CREATE UNIQUE INDEX ON mytable (name, coalesce(deleted, 'infinity'));
索引不能延迟,但您可以像这样交换值:
UPDATE mytable SET deleted = '-infinity' WHERE some_key = <x>;
UPDATE mytable SET deleted = <original value from row x> WHERE some_key = <y>;
UPDATE mytable SET deleted = NULL WHERE some_key = <x>;