SET CONSTRAINTS ALL DEFERRED 未按预期工作

SET CONSTRAINTS ALL DEFERRED not working as expected

在 PostgreSQL 9.3 数据库中,如果我定义表 ab 如下:

CREATE TABLE a(i integer);
ALTER TABLE a ADD CONSTRAINT pkey_a PRIMARY KEY (i);
CREATE TABLE b(j integer);
ALTER TABLE b add CONSTRAINT fkey_ij FOREIGN KEY (j)
      REFERENCES a (i) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE;
INSERT INTO a(i) VALUES(1);

然后执行以下操作:

START TRANSACTION;
SET CONSTRAINTS ALL DEFERRED;
INSERT INTO b(j) VALUES(2);
INSERT INTO a(i) VALUES(2);
COMMIT;

它产生以下错误。为什么 SET CONSTRAINTS 没有达到预期的效果?

ERROR: insert or update on table "b" violates foreign key constraint "fkey_ij"
SQL state: 23503 Detail: Key (j)=(2) is not present in table "a".

只能延迟 DEFERRABLE 个约束。

让我先推荐更好的选择:

1。 INSERT顺序

反转 INSERT 语句的 顺序 ,无需延迟任何内容。最简单和最快的 - 如果可能的话。

2。单个命令

单个命令中完成。然后仍然不需要延迟任何内容,因为在每个命令之后检查了不可延迟的约束并且 CTE 被认为是单个命令的一部分:

WITH ins1 AS (
   INSERT INTO b(j) VALUES(2)
   )
INSERT INTO a(i) VALUES(2);

同时,您可以重用第一个值 INSERT:对于某些情况或多行插入更安全/更方便:

WITH ins1 AS (
   INSERT INTO b(j) VALUES(3)
   RETURNING j
   )
INSERT INTO a(i)
SELECT j FROM ins1;

但是我需要延迟约束! (真的吗?)

ALTER TABLE b ADD CONSTRAINT fkey_ij FOREIGN KEY (j)
   REFERENCES a (i) MATCH SIMPLE
   ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE;  -- !!!

那么您的原始代码就可以工作了(有点慢,因为延迟约束会增加成本)。

db<>fiddle here

相关:

  • Constraint defined DEFERRABLE INITIALLY IMMEDIATE is still DEFERRED?

我原来的回答quoted the manual

Referential actions other than the NO ACTION check cannot be deferred, even if the constraint is declared deferrable.

但这具有误导性,因为它仅适用于“引用操作”,即对引用的 table 中的行发生 ON UPDATEON DELETE 的情况。手头的案例不是其中之一 - 如 .

我同意其他人的看法,正确的方法是按正确的顺序进行 - 但有时这不是一个可行的选择,需要更简单的方法才能在时间预算内完成工作。

如果这对任何人有帮助,我制作了一个程序,可以自动将延迟选项添加到所有 FK,以便

SET CONSTRAINTS ALL DEFERRED;

命令将起作用。当然只在必要时使用它。

DO
$$
DECLARE
    temp_rec RECORD;
    sql_exe TEXT;
BEGIN

sql_exe := $sql$
ALTER TABLE %1$s ALTER CONSTRAINT %2$s DEFERRABLE;
$sql$
;

FOR temp_rec IN 

(select constraint_name, table_name from information_schema.table_constraints where constraint_type = 'FOREIGN KEY')

LOOP
    EXECUTE format(sql_exe, temp_rec.table_name, temp_rec.constraint_name);
END LOOP;

END;
$$
LANGUAGE plpgsql
;