web-dev-qa-db-ja.com

postgresの遅延可能な一意のインデックス

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; 使用されている)。

私の仮定は正しいですか。正しい場合、意図した動作を実現する方法はありますか?

ありがとう

14
jcristovao

インデックスを据え置くことはできません-UNIQUEであるかどうか、部分的であるかどうかに関係なく、UNIQUE制約のみです。他のタイプの制約(FOREIGN KEYPRIMARY KEYEXCLUDE)も延期可能ですが、CHECK制約は遅延できません。

したがって、一意の部分インデックス(およびそれが実装する暗黙の制約)は、トランザクションの最後ではなく、すべてのステートメントで(そして実際には現在の実装でのすべての行の挿入/更新後に)チェックされます。


この制約を据え置きとして実装したい場合にできることは、デザインにテーブルをもう1つ追加することです。このようなもの:

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)。


もう1つの方法は、EXCLUDE制約を(ab)使用することです。

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でテストされています。

上記の内容:

  • booking_statusがnullまたは1以外の場合、CASE式はNULLになります。(CASE WHEN booking_status = 1 THEN TRUE END)(booking_status = 1 OR NULL)と書くと、さらに明確になります。

  • 一意制約と除外制約は、1つ以上の式がNULLである行を受け入れます。したがって、これはWHERE booking_status = 1でフィルターされたインデックスとして機能します。

  • WITH演算子はすべて=であるため、UNIQUE制約として機能します。

  • これら2つを組み合わせると、制約はフィルター処理された一意のインデックスとして機能します。

  • しかし、これは制約であり、EXCLUDE制約は据え置くことができます。

15
ypercubeᵀᴹ

この質問は何年も経過していますが、スペイン語を話す人のために明確にしたいと思います。テストは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;

クエリはOK、0行が影響を受けました(実行時間:0 ms;合計時間:0 ms)クエリはOK、1328行が影響を受けました(実行時間:858 ms;合計時間:858 ms)エラー:llave duplicada violarestricciónde unicidad"unique_div_nkit"DETAIL :存在します(div_nkit)=(1338)。

ここでSIは、最初の完全な文全体(1328行)を実行するため、主キーを変更できます。ただし、トランザクション(BEGIN)内ではありますが、CONSTRAINTはCOMMITを行わずに各文が終了するとすぐに検証されるため、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制約の詳細

延期不可、または延期可能で最初は即時

1
David Campos