it-swarm.com.de

Aufschiebbarer eindeutiger Index in Postgres

Wenn man sich postgres-Dokumentation für alter table ansieht, scheint es, dass reguläre Einschränkungen als DEFERRABLE markiert werden können (genauer gesagt, INITIALLY DEFERRED, Was mich interessiert).

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

Der Index darf weder Ausdrucksspalten noch Teilindex sein

Was mich zu der Annahme führt, dass es derzeit keine Möglichkeit gibt, einen eindeutigen Index mit Bedingungen wie:

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

INITIALLY DEFERRED Zu sein, was bedeutet, dass die Eindeutigkeitsbeschränkung nur am Ende der Transaktion überprüft wird (wenn SET CONSTRAINTS ALL DEFERRED; Verwendet wird).

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

Vielen Dank

14
jcristovao

Ein Index kann nicht zurückgestellt werden - egal ob es sich um UNIQUE handelt oder nicht, teilweise oder nicht, nur um eine UNIQUE Einschränkung. Andere Arten von Einschränkungen (FOREIGN KEY, PRIMARY KEY, EXCLUDE) sind ebenfalls aufschiebbar - jedoch nicht CHECK Einschränkungen.

Daher wird der eindeutige Teilindex (und die implizite Einschränkung, die er implementiert) bei jeder Anweisung (und tatsächlich nach jedem Einfügen/Aktualisieren von Zeilen in der aktuellen Implementierung) und nicht am Ende der Transaktion überprüft.


Wenn Sie diese Einschränkung als aufschiebbar 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
  ) ;

Mit diesem Design und unter der Annahme, dass booking_status Nur zwei mögliche Optionen (0 und 1) hat, können Sie es vollständig aus booking entfernen (wenn es bei booking_status Eine Zeile gibt, ist es 1, wenn nicht 0).


Eine andere Möglichkeit wäre, (ab) eine EXCLUDE -Einschränkung 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 tut:

  • Der Ausdruck CASE wird zu NULL, wenn booking_status Null oder anders als 1 ist. Wir könnten (CASE WHEN booking_status = 1 THEN TRUE END) Als (booking_status = 1 OR NULL) Schreiben, wenn dies dazu führt klarer.

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

  • Alle WITH -Operatoren sind =, Daher fungiert sie als UNIQUE -Einschränkung.

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

  • Aber es ist eine Einschränkung und EXCLUDE Einschränkungen können verschoben werden.

15
ypercubeᵀᴹ

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

Dadurch wird ein Standardprimärschlüssel erstellt, der für die Tabelle NICHT VERZÖGERT ist. Wenn Sie also das nächste UPDATE versuchen, wird folgende Fehlermeldung angezeigt:

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 REIHE überprüft, ob die Einschränkung oder Einschränkung erfüllt ist.


Das CONSTRAINT IMMEDIATE wird jetzt 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 : Ya existe la llave (div_nkit) = (1338).

Hier erlaubt SI das Ändern des Primärschlüssels, da er den gesamten ersten vollständigen Satz (1328 Zeilen) ausführt; Obwohl es sich um eine Transaktion (BEGIN) handelt, wird CONSTRAINT sofort nach Beendigung jedes Satzes validiert, ohne COMMIT ausgeführt zu haben, und generiert daher den Fehler beim Ausführen von INSERT. Schließlich haben wir die CONSTRAINT DEFERRED erstellt, um Folgendes zu tun:

**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 **, jeden Satz separat ausführen, wird kein Fehler für das INSERT generiert, da es nicht validiert wird, aber das endgültige COMMIT ausgeführt wird, wenn es eine Inkonsistenz findet.


Für vollständige Informationen auf Englisch empfehle ich Ihnen, die Links zu überprüfen:

Deferrable SQL Constraints in Depth

NICHT VERZÖGERBAR versus VERZÖGERBAR SOFORT SOFORT

1
David Campos