Indice univoco differibile in postgres


14

Esaminando la documentazione di Postgres per alter table , sembra che i vincoli regolari possano essere contrassegnati come DEFERRABLE(più concretamente INITIALLY DEFERRED, che è quello che mi interessa).

Gli indici possono anche essere associati a un vincolo, purché:

L'indice non può avere colonne di espressioni né essere un indice parziale

Il che mi porta a credere che attualmente non c'è modo di avere un indice univoco con condizioni, come:

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

Essere INITIALLY DEFERRED, nel senso, che il "vincolo" di unicità verrà verificato solo alla fine della transazione (se SET CONSTRAINTS ALL DEFERRED;utilizzato).

La mia ipotesi è corretta e, in tal caso, esiste un modo per raggiungere il comportamento previsto?

Grazie

Risposte:


15

Un indice non può essere rinviato, non importa se è UNIQUEo meno, parziale o no, solo un UNIQUEvincolo. Altri tipi di vincoli ( FOREIGN KEY, PRIMARY KEY, EXCLUDE) sono anche differibile - ma non CHECKvincoli.

Quindi l'indice parziale univoco (e il vincolo implicito che implementa) verrà verificato in ogni istruzione (e in effetti dopo ogni inserimento / aggiornamento di riga nell'attuale implementazione), non alla fine della transazione.


Quello che potresti fare, se vuoi implementare questo vincolo come differibile, è aggiungere un'altra tabella nel progetto. Qualcosa come questo:

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

Con questo design e supponendo che booking_statusabbia solo 2 opzioni possibili (0 e 1), è possibile rimuoverlo completamente da booking(se c'è una riga in booking_status, è 1, se non è 0).


Un altro modo sarebbe (ab) usare un EXCLUDEvincolo:

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 ;

Testato su dbfiddle .

Cosa fa quanto sopra:

  • L' CASEespressione diventa NULLquando booking_statusè nulla o diversa da 1. Potremmo scrivere (CASE WHEN booking_status = 1 THEN TRUE END)come (booking_status = 1 OR NULL)se ciò lo rendesse più chiaro.

  • I vincoli univoci ed esclusi accettano righe in cui una o più espressioni sono NULL. Quindi agisce come un indice filtrato con WHERE booking_status = 1.

  • Tutti gli WITHoperatori lo fanno =quindi agisce come un UNIQUEvincolo.

  • Questi due elementi combinati fanno sì che il vincolo funga da indice univoco filtrato.

  • Ma è un vincolo e i EXCLUDEvincoli possono essere rinviati.


2
+1 per la versione EXCLUDE, era quello di cui avevo bisogno. Ecco un altro esempio che mostra le capacità di EXCLUDE: cybertec-postgresql.com/en/postgresql-exclude-beyond-unique
Benjamin Peter,

(CASE WHEN booking_status = 1 THEN TRUE END) WITH =)dovrebbe essere sostituito con ) WHERE (booking_status = 1)"I vincoli di esclusione sono implementati usando un indice" e questo indice parziale con WHEREsarà più piccolo e più veloce - postgresql.org/docs/current/sql-createtable.html e postgresql.org/docs/current/sql- createindex.html
Denis Ryzhkov,

1

Sebbene siano trascorsi gli anni di questa domanda, vorrei chiarire per i parlanti spagnoli, i test sono stati eseguiti su Postgres:

Il seguente vincolo è stato aggiunto a una tabella di 1337 record, in cui il kit è la chiave primaria:

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

Questo crea una chiave primaria predefinita NON DEFERRED per la tabella, quindi quando proviamo il prossimo AGGIORNAMENTO otteniamo un errore:

update ele_kitscompletos
set div_nkit = div_nkit + 1; 

ERRORE: la chiave duplicata viola la restrizione di unicità «unique_div_nkit»

In Postgres, l'esecuzione di un AGGIORNAMENTO per ogni ROW verifica che la RESTRIZIONE o VINCITORE sia soddisfatta.


Il VINCITORE IMMEDIATO è ora creato e ogni istruzione viene eseguita separatamente:

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;

Query OK, 0 righe interessate (tempo di esecuzione: 0 ms; tempo totale: 0 ms) Query OK, 1328 righe interessate (tempo di esecuzione: 858 ms; tempo totale: 858 ms) ERRORE: llave duplicada viola restricción de unicidad «unique_div_nkit» DETTAGLIO : Ya existe la llave (div_nkit) = (1338).

Qui SI consente di modificare la chiave primaria poiché esegue l'intera prima frase completa (1328 righe); ma sebbene sia in transazione (INIZIO), il VINCITORE viene validato immediatamente al termine di ogni frase senza aver commesso COMMIT, quindi genera l'errore durante l'esecuzione di INSERT. Alla fine abbiamo creato il VINCITORE DEFERRED nel modo seguente:

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

Se eseguiamo ogni istruzione di ** Blocco 2 **, ogni frase separatamente, non viene generato alcun errore per INSERT poiché non viene convalidato, ma il COMMIT finale viene eseguito dove trova un'incoerenza.


Per informazioni complete in inglese ti consiglio di controllare i link:

Vincoli SQL differibili in profondità

NON DEFERRABILE CONTRO DEFERRABILE INIZIALMENTE IMMEDIATO

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.