Postgres中的可延迟唯一索引


14

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

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

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

这使我相信,目前尚无办法根据条件创建唯一索引,例如:

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

INITIALLY DEFERRED,这意味着,该独特性“约束”将只在事务结束进行验证(如果SET CONSTRAINTS ALL DEFERRED;使用)。

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

谢谢

Answers:


15

索引不能被延迟-索引是否UNIQUE存在,部分UNIQUE约束与否,仅约束无关紧要。其它类型的约束(FOREIGN KEYPRIMARY KEYEXCLUDE)也可推迟-但不是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
  ) ;

通过这种设计,并假设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 ;

dbfiddle测试。

以上是做什么的:

  • CASE表达式变为NULLbooking_status为空或大于1的不同,我们可以写(CASE WHEN booking_status = 1 THEN TRUE END)(booking_status = 1 OR NULL)如果让任何更加清晰。

  • 唯一约束和排除约束接受其中一个或多个表达式为NULL的行。因此它充当的过滤索引WHERE booking_status = 1

  • 所有的WITH运营商=,所以它作为一个UNIQUE约束。

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

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


2
我需要EXCLUDE版本的+1。这是另一个展示EXCLUDE功能的示例:cybertec-postgresql.com/en/postgresql-exclude-beyond-unique
Benjamin Peter

(CASE WHEN booking_status = 1 THEN TRUE END) WITH =)应该用替换,) WHERE (booking_status = 1)因为“排除约束是使用索引实现的”,并且该部分索引WHERE将变得更小和更快-postgresql.org/docs/current/sql-createtable.htmlpostgresql.org/docs/current/sql- createindex.html
Denis Ryzhkov

1

尽管这个问题已经过去了多年,但我想为讲西班牙语的人澄清一下,这些测试已在Postgres中完成:

以下约束已添加到1337条记录的表中,其中该工具包是主键:

**Bloque 1**
ALTER TABLE ele_kitscompletos
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit) 

这会为表创建一个默认的主键NOT DEFERRED,因此在尝试下一个UPDATE时会出现错误:

update ele_kitscompletos
set div_nkit = div_nkit + 1; 

错误:重复键违反了唯一性限制«unique_div_nkit»

在Postgres中,对每个ROW执行UPDATE都会验证是否满足RESTRICTION或CONSTRAINT。


现在创建了CONSTRAINT IMMEDIATE,并且分别执行了每个语句:

ALTER TABLE ele_kitscompletos
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit)
DEFERRABLE INITIALLY IMMEDIATE

**Bloque 2**
BEGIN;   
UPDATE ele_kitscompletos set div_nkit = div_nkit + 1;
INSERT INTO public.ele_kitscompletos(div_nkit, otro_campo)
VALUES 
  (1338, '888150502');
COMMIT;

查询正常,受影响的0行(执行时间:0 ms;总时间:0 ms)查询正常,受影响的1328行(执行时间:858 ms;总时间:858 ms)错误:将取消«unique_div_nkit»的详细信息:您的存在(div_nkit)=(1338)。

在这里,SI允许更改主键,因为它执行了整个第一完整句子(1328行)。但是,尽管它处于事务处理(BEGIN)中,但在完成每个语句而没有进行COMMIT时会立即验证CONSTRAINT,因此在执行INSERT时会产生错误。最后,我们创建了CONSTRAINT DEFERRED,请执行以下操作:

**Bloque 3**
ALTER TABLE public.ele_edivipol
DROP CONSTRAINT unique_div_nkit RESTRICT;   

ALTER TABLE ele_edivipol
ADD CONSTRAINT unique_div_nkit
PRIMARY KEY (div_nkit)
DEFERRABLE INITIALLY DEFERRED

如果我们分别对每个语句分别执行**块2 **的每个语句,则不会对INSERT生成任何错误,因为它不进行验证,但是最终COMMIT在发现不一致的地方执行。


有关英文的完整信息,建议您检查以下链接:

深度的可延迟SQL约束

不可延迟与立即可延迟

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.