postgres 中的可延迟唯一索引

jcr*_*vao 16 postgresql index transaction unique-constraint

查看用于 alter table 的 postgres 文档,似乎可以将常规约束标记为DEFERRABLE(更具体地说,INITIALLY DEFERRED,这是我感兴趣的)。

索引也可以与约束相关联,只要:

索引不能有表达式列,也不能是部分索引

这让我相信目前没有办法拥有一个具有条件的唯一索引,例如:

CREATE UNIQUE INDEX unique_booking
  ON public.booking
  USING btree
  (check_in, check_out)
  WHERE booking_status = 1;
Run Code Online (Sandbox Code Playgroud)

INITIALLY DEFERRED,意思是,唯一性“约束”只会在交易结束时进行验证(如果SET CONSTRAINTS ALL DEFERRED;使用)。

我的假设是否正确,如果正确,有什么方法可以实现预期的行为?

谢谢

ype*_*eᵀᴹ 18

索引不能被推迟——不管它是UNIQUE与否,部分与否,只是一个UNIQUE约束。其他类型的约束 ( FOREIGN KEY, PRIMARY KEY, EXCLUDE) 也是可延迟的 - 但不是CHECK约束。

所以唯一的部分索引(及其实现的隐式约束)将在每个语句(实际上在当前实现中的每一行插入/更新之后)进行检查,而不是在事务结束时。


如果您想将此约束实现为可延迟,您可以做的是在设计中再添加一个表。像这样的东西:

CREATE TABLE public.booking_status
  ( booking_id int NOT NULL,               -- same types
    check_in timestamp NOT NULL,           -- as in  
    check_out timestamp NOT NULL,          -- booking
    CONSTRAINT unique_booking
        UNIQUE (check_in, check_out)
        DEFERRABLE INITIALLY DEFERRED,
    CONSTRAINT unique_booking_fk
        FOREIGN KEY (booking_id, check_in, check_out)
        REFERENCES public.booking (booking_id, check_in, check_out)
        DEFERRABLE INITIALLY DEFERRED
  ) ;
Run Code Online (Sandbox Code Playgroud)

使用这种设计并假设booking_status只有 2 个可能的选项(0 和 1),您可以将其完全删除booking(如果在 处有一行booking_status,则为 1,否则为 0)。


另一种方法是(ab)使用EXCLUDE约束:

ALTER TABLE booking
    ADD CONSTRAINT unique_booking
        EXCLUDE 
          ( check_in  WITH =, 
            check_out WITH =, 
            (CASE WHEN booking_status = 1 THEN TRUE END) WITH =
          ) 
        DEFERRABLE INITIALLY DEFERRED ;
Run Code Online (Sandbox Code Playgroud)

dbfiddle测试。

以上是做什么的:

  • 当为 null 或不同于 1时,CASE表达式变为。我们可以这样写,好像这会使它更清楚。NULLbooking_status(CASE WHEN booking_status = 1 THEN TRUE END)(booking_status = 1 OR NULL)

  • 唯一和排除约束接受一个或多个表达式为 NULL 的行。所以它作为一个过滤索引WHERE booking_status = 1

  • 所有的WITH操作符都是=这样作为UNIQUE约束的。

  • 这两个组合使约束充当过滤的唯一索引。

  • 但这是一个约束,EXCLUDE可以推迟约束。


上述方法的改进(感谢Denis Ryzhkov)是使用部分(过滤的)EXCLUDE 约束。使用更少的空间(与部分索引相同)并且是可延迟的:

ALTER TABLE booking
    ADD CONSTRAINT unique_booking
        EXCLUDE 
          ( check_in  WITH =, 
            check_out WITH =
          ) 
        WHERE (booking_status = 1)
        DEFERRABLE INITIALLY DEFERRED ;
Run Code Online (Sandbox Code Playgroud)

dbfiddle-2测试。

  • EXCLUDE 版本的 +1,正是我所需要的。这是显示 EXCLUDE 功能的另一个示例:https://www.cybertec-postgresql.com/en/postgresql-exclude-beyond-unique/ (2认同)