Odroczony unikalny indeks w postgresie

14

Patrząc na dokumentację postgres dla tabeli zmian , wydaje się, że regularne ograniczenia można oznaczyć jako DEFERRABLE(bardziej konkretnie, INITIALLY DEFERREDco mnie interesuje).

Indeksy można również powiązać z ograniczeniem, o ile:

Indeks nie może mieć kolumn wyrażeń ani być indeksem częściowym

Co prowadzi mnie do wniosku, że obecnie nie ma sposobu na stworzenie unikalnego indeksu z warunkami, takimi jak:

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

Być INITIALLY DEFERRED, co oznacza, że wyjątkowość „przymus” będą weryfikowane tylko na koniec transakcji (jeśli SET CONSTRAINTS ALL DEFERRED;jest używany).

Czy moje założenie jest prawidłowe, a jeśli tak, to czy jest jakiś sposób na osiągnięcie zamierzonego zachowania?

Dzięki

jcristovao
źródło

Odpowiedzi:

15

Indeks nie może być odroczony - nie ma znaczenia, czy jest, UNIQUEczy nie, częściowy czy nie, jest tylko UNIQUEograniczeniem. Inne rodzaje ograniczeń ( FOREIGN KEY, PRIMARY KEY, EXCLUDE) są również odroczeniu - ale nie CHECKograniczenia.

Tak więc unikalny indeks częściowy (i domniemane ograniczenie, które implementuje) będzie sprawdzany przy każdej instrukcji (i faktycznie po każdym wstawieniu / aktualizacji wiersza w bieżącej implementacji), a nie na końcu transakcji.


Jeśli chcesz zaimplementować to ograniczenie jako możliwe do odroczenia, możesz dodać jeszcze jedną tabelę w projekcie. Coś takiego:

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

Przy takim projekcie i przy założeniu, że booking_statusma tylko 2 możliwe opcje (0 i 1), możesz go całkowicie usunąć booking(jeśli jest wiersz w booking_status, to jest 1, jeśli nie jest 0).


Innym sposobem byłoby (ab) użycie EXCLUDEograniczenia:

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 ;

Testowany w dbfiddle .

Co robi powyższe:

  • CASEEkspresja staje NULLgdy booking_statusjest zerowy lub inny niż 1. Moglibyśmy napisać (CASE WHEN booking_status = 1 THEN TRUE END)jak (booking_status = 1 OR NULL)gdyby czyni go bardziej jasne.

  • Unikalne i wykluczające ograniczenia akceptują wiersze, w których jedno lub więcej wyrażeń ma wartość NULL. Działa więc jak filtrowany indeks z WHERE booking_status = 1.

  • Wszyscy WITHoperatorzy są, =więc działa to jako UNIQUEograniczenie.

  • Te dwa połączone sprawiają, że ograniczenie działa jak przefiltrowany unikalny indeks.

  • Ale jest to ograniczenie i EXCLUDEograniczenia można odroczyć.

ypercubeᵀᴹ
źródło
2
Potrzebowałem +1 dla wersji EXCLUDE. Oto kolejny przykład pokazujący możliwości EXCLUDE: cybertec-postgresql.com/en/postgresql-exclude-beyond-unique
Benjamin Peter
(CASE WHEN booking_status = 1 THEN TRUE END) WITH =)należy zastąpić, ) WHERE (booking_status = 1)ponieważ „Ograniczenia wykluczające są wdrażane przy użyciu indeksu”, a ten częściowy indeks z WHEREbędzie mniejszy i szybszy - postgresql.org/docs/current/sql-createtable.html i postgresql.org/docs/current/sql- createindex.html
Denis Ryzhkov
1

Chociaż lata tego pytania minęły, chciałbym wyjaśnić dla hiszpańskojęzycznych, testy zostały wykonane w Postgres:

Do tabeli 1337 rekordów dodano następujące ograniczenie, w którym zestaw jest kluczem podstawowym:

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

Tworzy to domyślny klucz podstawowy NIE ODNIESIONY dla tabeli, więc przy próbie następnej aktualizacji otrzymujemy błąd:

update ele_kitscompletos
set div_nkit = div_nkit + 1; 

BŁĄD: duplikat klucza narusza ograniczenie unikatowości «unique_div_nkit»

W Postgres wykonanie AKTUALIZACJI dla każdego WIERSZA sprawdza, czy OGRANICZENIE lub OGRANICZENIE jest spełnione.


Utworzono NATYCHMIASTOWY OGRANICZNIK, a każda instrukcja jest wykonywana osobno:

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;

Zapytanie OK, wpłynęło 0 wierszy (czas wykonania: 0 ms; całkowity czas: 0 ms) Zapytanie OK, wpłynęło 1328 wierszy (czas wykonania: 858 ms; całkowity czas: 858 ms) BŁĄD: llave duplicada viola resticción de unicidad «unikalny_div_nkit» SZCZEGÓŁ : Ya existe la llave (div_nkit) = (1338).

Tutaj SI pozwala zmienić klucz podstawowy, ponieważ wykonuje całe pierwsze pełne zdanie (1328 wierszy); ale chociaż jest w transakcji (POCZĄTEK), OGRANICZENIE jest sprawdzane natychmiast po zakończeniu każdego zdania bez wykonania polecenia COMMIT, dlatego generuje błąd podczas wykonywania WSTAWIANIA. Wreszcie stworzyliśmy CONSTRRAINT DEFERRED, wykonując następujące czynności:

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

Jeśli wykonamy każdą instrukcję ** Bloku 2 **, każde zdanie osobno, INSERT nie zostanie wygenerowany błąd, ponieważ nie sprawdza poprawności, ale wykonuje się ostateczne polecenie COMMIT w przypadku stwierdzenia niespójności.


Aby uzyskać pełne informacje w języku angielskim, sugeruję sprawdzenie linków:

Głębokie ograniczenia SQL

NIE ZNALEZIONE w porównaniu do ZNACZNIE NATYCHMIASTOWEJ NATYCHMIASTU

David Campos
źródło