如何在 PostgreSQL 中使用空值创建可延迟的唯一约束?

问题描述 投票:0回答:1

我正在 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 database-design unique-constraint indices postgresql-14
1个回答
0
投票

三种可能的解决方案:

  1. 这是最好的,也是你不想听的:
    升级到 PostgreSQL v16 并将约束定义为

    UNIQUE NULLS NOT DISTINCT

  2. 不要为未删除的行存储 NULL,而是存储

    infinity

    然后您可以将
    deleted
    声明为
    NOT NULL
    并创建一个正常的唯一约束。

  3. 不要创建约束,而是创建唯一索引:

    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>;
    
© www.soinside.com 2019 - 2024. All rights reserved.