Indeks unik yang ditangguhkan di postgres


14

Melihat ke dokumentasi postgres untuk tabel alter , tampaknya kendala reguler dapat ditandai sebagai DEFERRABLE(lebih konkret INITIALLY DEFERRED,, itulah yang saya tertarik).

Indeks juga dapat dikaitkan dengan kendala, selama:

Indeks tidak dapat memiliki kolom ekspresi atau indeks parsial

Yang membuat saya percaya saat ini tidak ada cara untuk memiliki indeks unik dengan ketentuan, seperti:

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

Untuk menjadi INITIALLY DEFERRED, yang berarti, bahwa 'kendala' keunikan hanya akan diverifikasi pada akhir transaksi (jika SET CONSTRAINTS ALL DEFERRED;digunakan).

Apakah asumsi saya benar, dan jika demikian, apakah ada cara untuk mencapai perilaku yang dimaksud?

Terima kasih

Jawaban:


15

Indeks tidak dapat ditunda - tidak masalah apakah itu UNIQUEsebagian atau tidak, hanya UNIQUEkendala. Jenis lain dari kendala ( FOREIGN KEY, PRIMARY KEY, EXCLUDE) juga deferrable - tetapi tidak CHECKkendala.

Jadi indeks parsial unik (dan kendala implisit yang diterapkannya) akan diperiksa pada setiap pernyataan (dan faktanya setelah setiap baris memasukkan / memperbarui dalam implementasi saat ini), bukan pada akhir transaksi.


Apa yang bisa Anda lakukan, jika Anda ingin mengimplementasikan batasan ini sebagai ditangguhkan, adalah menambahkan satu tabel lagi dalam desain. Sesuatu seperti ini:

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

Dengan desain ini dan dengan asumsi bahwa booking_statushanya memiliki 2 opsi yang memungkinkan (0 dan 1), Anda dapat menghapus seluruhnya dari booking(jika ada baris pada booking_status, itu adalah 1, jika tidak adalah 0).


Cara lain adalah dengan (ab) menggunakan EXCLUDEbatasan:

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 ;

Diuji di dbfiddle .

Apa yang dilakukan di atas:

  • The CASEekspresi menjadi NULLsaat booking_statusnull atau berbeda dari 1. Kita bisa menulis (CASE WHEN booking_status = 1 THEN TRUE END)seperti (booking_status = 1 OR NULL)apakah yang membuat itu lagi jelas.

  • Batasan unik dan kecualikan menerima baris di mana satu atau lebih ekspresi NULL. Jadi berfungsi sebagai indeks yang difilter dengan WHERE booking_status = 1.

  • Semua WITHoperator =itu bertindak sebagai UNIQUEkendala.

  • Gabungan keduanya ini membuat batasan bertindak sebagai indeks unik yang difilter.

  • Tapi itu kendala dan EXCLUDEkendala bisa ditunda.


2
+1 untuk versi EXCLUDE, adalah yang saya butuhkan. Berikut adalah contoh lain yang menunjukkan kemampuan EXCLUDE: cybertec-postgresql.com/en/postgresql-exclude-beyond-unique
Benjamin Peter

(CASE WHEN booking_status = 1 THEN TRUE END) WITH =)harus diganti dengan ) WHERE (booking_status = 1)karena "Pengecualian kendala diimplementasikan menggunakan indeks", dan indeks parsial ini WHEREakan lebih kecil dan lebih cepat - postgresql.org/docs/current/sql-createtable.html dan postgresql.org/docs/current/sql- createindex.html
Denis Ryzhkov

1

Meskipun tahun-tahun pertanyaan ini telah berlalu, saya ingin mengklarifikasi untuk penutur bahasa Spanyol, tes telah dilakukan di Postgres:

Kendala berikut telah ditambahkan ke tabel catatan 1337, di mana kit adalah kunci utama:

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

Ini menciptakan kunci utama default TIDAK DITAWARKAN untuk tabel sehingga ketika mencoba PEMBARUAN berikutnya kita mendapatkan kesalahan:

update ele_kitscompletos
set div_nkit = div_nkit + 1; 

GALAT: kunci duplikat melanggar batasan keunikan «unique_div_nkit»

Di Postgres, menjalankan UPDATE untuk setiap ROW memverifikasi bahwa PEMBATASAN atau KONSTRAINT terpenuhi.


SEGERA CONSTRAINT sekarang dibuat dan setiap pernyataan dieksekusi secara terpisah:

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;

Kueri OK, 0 baris terpengaruh (waktu eksekusi: 0 ms; total waktu: 0 ms) Kueri OK, 1328 baris terpengaruh (waktu eksekusi: 858 ms; total waktu: 858 ms) KESALAHAN: llave duplikat biola restricci de de unicidad «unique_div_nkit» DETAIL : Ya existe la llave (div_nkit) = (1338).

Di sini SI memungkinkan mengubah kunci utama karena mengeksekusi seluruh kalimat lengkap pertama (1328 baris); tetapi meskipun sedang dalam transaksi (BEGIN), CONSTRAINT segera divalidasi setelah menyelesaikan setiap kalimat tanpa membuat KOMIT, oleh karena itu menghasilkan kesalahan ketika menjalankan INSERT. Akhirnya kami membuat CONSTRAINT DEFERRED melakukan hal berikut:

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

Jika kami menjalankan setiap pernyataan ** Blok 2 **, setiap kalimat secara terpisah, tidak ada kesalahan yang dihasilkan ke INSERT karena itu tidak valid tetapi KOMIT akhir dieksekusi di mana ia menemukan ketidakkonsistenan.


Untuk informasi lengkap dalam bahasa Inggris, saya sarankan Anda memeriksa tautannya:

Kendala SQL yang Ditangguhkan dalam Kedalaman

BUKAN DITANGGUNG JAWAB SEGERA DITERBITKAN

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.