Відмітний унікальний індекс у постгресах


14

Переглядаючи документацію постгресів для таблиці alter , здається, що регулярні обмеження можуть бути позначені як 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;вона використовується).

Чи правильне моє припущення, і якщо так, чи є спосіб досягти наміченої поведінки?

Спасибі

Відповіді:


15

Індекс не може бути відкладений - неважливо, є він UNIQUEчи ні, частковий чи ні, лише UNIQUEобмеження. Інші типи обмежень ( FOREIGN KEY, PRIMARY KEY, EXCLUDE) також відкладаються - але не CHECKобмеження.

Таким чином, унікальний частковий індекс (і імпліцитне обмеження, яке він реалізує) перевірятиметься у кожному операторі (і фактично після кожного вставлення / оновлення рядків у поточній реалізації), а не в кінці транзакції.


Що ви можете зробити, якщо хочете реалізувати це обмеження як відкладене, це додати ще одну таблицю в дизайн. Щось на зразок цього:

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


Іншим способом було б (ab) використання EXCLUDEобмеження:

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 .

Що робить вище:

  • CASEВираз стає , NULLколи booking_statusдорівнює нулю або відрізняється від 1. Ми могли б написати , (CASE WHEN booking_status = 1 THEN TRUE END)як (booking_status = 1 OR NULL)якщо це робить будь-який більш ясним.

  • Унікальні та виключаючі обмеження приймають рядки, де один або більше виразів NULL. Таким чином, він діє як відфільтрований індекс з WHERE booking_status = 1.

  • Усі WITHоператори є =такими, що це діє як UNIQUEобмеження.

  • Ці два комбіновані форми обмеження діють як відфільтрований унікальний індекс.

  • Але це обмеження, і EXCLUDEобмеження можуть бути відкладені.


2
+1 для версії EXCLUDE - це те, що мені було потрібно. Ось ще один приклад, що демонструє можливості EXCLUDE: cybertec-postgresql.com/en/postgresql-exclude-beyond-unique
Бенджамін Пітер

(CASE WHEN booking_status = 1 THEN TRUE END) WITH =)слід замінити тим, ) WHERE (booking_status = 1)що "Обмеження виключення реалізуються за допомогою індексу", і цей частковий індекс з WHEREбуде меншим та швидшим - postgresql.org/docs/current/sql-createtable.html та postgresql.org/docs/current/sql- createindex.html
Денис Рижков

1

Хоча роки цього питання минули, я хотів би уточнити для іспанських, хто виступає, тести були зроблені в Postgres:

Наступне обмеження було додано до таблиці 1337 записів, де набір є основним ключем:

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

Це створює первинний ключ за замовчуванням НЕ ВИЗНАЧЕНО для таблиці, тому при спробі наступного ОНОВЛЕННЯ ми отримуємо помилку:

update ele_kitscompletos
set div_nkit = div_nkit + 1; 

ПОМИЛКА: дублюючий ключ порушує обмеження унікальності «unique_div_nkit»

У Postgres, виконуючи ОНОВЛЕННЯ для кожного ROW, перевіряється, чи дотримано 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;

Запит ОК, 0 рядків зачеплені (час виконання: 0 мс; загальний час: 0 мс) Запит ОК, 1328 рядків, які постраждали (час виконання: 858 мс; загальний час: 858 мс) ПОМИЛКА: помилка дублікату обмеження обмеження «унікальний_дів_кіт» ДЕТАЛІ : Ya existe la llave (div_nkit) = (1338).

Тут SI дозволяє змінювати первинний ключ, оскільки він виконує все перше повне речення (1328 рядків); але, хоча це є транзакцією (BEGIN), КОНСТРАНТ перевіряється відразу після закінчення кожного речення, не складаючи COMMIT, тому створює помилку під час виконання ВСТУП. Нарешті ми створили ВІДКРИТИЙ ВИМОГ, зробивши наступне:

**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 в глибині

НЕ ЗАМОВЛЕННЯ проти ВІДПОВІДНОГО ІНТИЦІАЛЬНО НЕМАТИЧНОГО

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.