Aufschiebbarer eindeutiger Index in Postgres

14

Ein Blick in die postgres-Dokumentation für alter table zeigt , dass reguläre Einschränkungen als gekennzeichnet werden können DEFERRABLE(genauer gesagtINITIALLY DEFERRED , was mich interessiert).

Indizes können auch einer Einschränkung zugeordnet werden, sofern:

Der Index darf weder Ausdrucksspalten noch einen Teilindex enthalten

Was mich zu der Annahme bringt, dass es derzeit keine Möglichkeit gibt, einen eindeutigen Index mit Bedingungen zu erstellen, wie z.

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

Dies INITIALLY DEFERREDbedeutet, dass die Eindeutigkeit "Einschränkung" nur am Ende der Transaktion überprüft wird (sofern SET CONSTRAINTS ALL DEFERRED;verwendet).

Ist meine Annahme richtig und wenn ja, gibt es eine Möglichkeit, das beabsichtigte Verhalten zu erreichen?

Vielen Dank

jcristovao
quelle

Antworten:

15

Ein Index kann nicht zurückgestellt werden - es spielt keine Rolle, ob es sich um eine UNIQUETeil- oder eine UNIQUENebenbedingung handelt oder nicht . Andere Arten von Beschränkungen ( FOREIGN KEY, PRIMARY KEY, EXCLUDE) sind auch aufschiebbare - aber nicht CHECKZwänge.

Daher wird der eindeutige Teilindex (und die implizite Einschränkung, die er implementiert) bei jeder Anweisung (und tatsächlich nach jeder Zeileneinfügung / -aktualisierung in der aktuellen Implementierung) und nicht am Ende der Transaktion überprüft.


Wenn Sie diese Einschränkung als verzögernd implementieren möchten, können Sie dem Entwurf eine weitere Tabelle hinzufügen. Etwas wie das:

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

Bei diesem Entwurf und unter der Annahme, dass booking_statusnur 2 Optionen (0 und 1) möglich sind, können Sie ihn vollständig entfernen booking(wenn sich eine Zeile auf befindet booking_status, ist es 1, wenn nicht 0).


Eine andere Möglichkeit wäre, eine EXCLUDEEinschränkung (ab) zu verwenden :

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 ;

Getestet bei dbfiddle .

Was das oben Genannte bewirkt:

  • Der CASEAusdruck wird, NULLwenn booking_statusnull oder anders als 1 ist. Wir könnten schreiben, (CASE WHEN booking_status = 1 THEN TRUE END)als (booking_status = 1 OR NULL)ob das alles klarer machen würde.

  • Eindeutige und ausschließende Bedingungen akzeptieren Zeilen, in denen einer oder mehrere der Ausdrücke NULL sind. Es wirkt also als gefilterter Index mit WHERE booking_status = 1.

  • Alle WITHOperatoren sind =so, dass es als UNIQUEEinschränkung wirkt .

  • Diese beiden zusammen bewirken, dass die Einschränkung als gefilterter eindeutiger Index fungiert.

  • Aber es ist eine Einschränkung und EXCLUDEEinschränkungen können aufgeschoben werden.

ypercubeᵀᴹ
quelle
2
+1 für die EXCLUDE-Version war das, was ich brauchte. Hier ist ein weiteres Beispiel, das die Funktionen von EXCLUDE zeigt: cybertec-postgresql.com/de/postgresql-exclude-beyond-unique
Benjamin Peter
(CASE WHEN booking_status = 1 THEN TRUE END) WITH =)sollte durch ersetzt werden, ) WHERE (booking_status = 1)da "Ausschlussbeschränkungen mithilfe eines Index implementiert werden" und dieser Teilindex mit WHEREkleiner und schneller ist - postgresql.org/docs/current/sql-createtable.html und postgresql.org/docs/current/sql- createindex.html
Denis
1

Obwohl die Jahre dieser Frage vergangen sind, möchte ich für spanischsprachige Personen klarstellen, dass die Tests in Postgres durchgeführt wurden:

Die folgende Einschränkung wurde zu einer Tabelle mit 1337 Datensätzen hinzugefügt, wobei das Kit der Primärschlüssel ist:

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

Dies erstellt einen Standardprimärschlüssel NOT DEFERRED für die Tabelle. Wenn Sie also das nächste UPDATE versuchen, erhalten Sie folgende Fehlermeldung:

update ele_kitscompletos
set div_nkit = div_nkit + 1; 

FEHLER: doppelter Schlüssel verletzt die Eindeutigkeitsbeschränkung «unique_div_nkit»

In Postgres wird durch Ausführen eines UPDATE für jede Zeile überprüft, ob die Einschränkung oder Einschränkung erfüllt ist.


Das CONSTRAINT IMMEDIATE wird nun erstellt und jede Anweisung wird separat ausgeführt:

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;

Abfrage OK, 0 Zeilen betroffen (Ausführungszeit: 0 ms; Gesamtzeit: 0 ms) Abfrage OK, 1328 Zeilen betroffen (Ausführungszeit: 858 ms; Gesamtzeit: 858 ms) FEHLER: Duplikat Viola Restricción de Unicidad «unique_div_nkit» DETAIL : Ya existe la llave (div_nkit) = (1338).

Hier ermöglicht SI das Ändern des Primärschlüssels, da er den gesamten ersten vollständigen Satz (1328 Zeilen) ausführt; Obwohl es sich in der Transaktion (BEGIN) befindet, wird der CONSTRAINT sofort nach Beendigung jedes Satzes validiert, ohne COMMIT ausgeführt zu haben. Daher wird beim Ausführen des INSERT ein Fehler generiert. Zum Schluss haben wir den CONSTRAINT DEFERRED erstellt und machen folgendes:

**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

Wenn wir jede Anweisung von ** Block 2 ** und jeden Satz einzeln ausführen, wird kein Fehler für das INSERT generiert, da es nicht validiert, sondern das endgültige COMMIT ausgeführt wird, wenn es eine Inkonsistenz feststellt.


Für vollständige Informationen in Englisch schlage ich vor, dass Sie die Links überprüfen:

Deferrable SQL-Einschränkungen im Detail

NOT DEFERRABLE versus DEFERRABLE INITIALLY SOFORT

David Campos
quelle