web-dev-qa-db-ja.com

SET CONSTRAINTS ALLDEFERREDが期待どおりに機能しない

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".
14
user79074

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 <>フィドル ここ

関連:


私の元の答え マニュアルを引用

制約が延期可能であると宣言されている場合でも、NO ACTIONチェック以外の参照アクションは延期できません。

ただし、これは「参照アクション」、つまり参照テーブルの行にON UPDATEまたはON DELETEが発生する場合にのみ適用されるため、誤解を招く恐れがありました。手元のケースはそれらの1つではありません @ zer0hedgeが指摘したように

12

私は他の人たちに同意します。それを行う正しい方法は正しい順序ですが、それが実行可能な選択肢ではなく、時間予算内で仕事を成し遂げるためにもっと簡単なことが必要な場合があります。

これが誰かに役立つ場合に備えて、すべてのFKに遅延オプションを追加することを自動化する手順を作成しました。

制約をすべて延期します。

コマンドは機能します。もちろん必要な場合にのみ使用してください。

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
;
0
Alexi Theodore