it-swarm.asia

Nilai-nilai unik batasan NULL dan multi-kolom PostgreSQL

Saya punya tabel seperti berikut:

create table my_table (
    id   int8 not null,
    id_A int8 not null,
    id_B int8 not null,
    id_C int8 null,
    constraint pk_my_table primary key (id),
    constraint u_constrainte unique (id_A, id_B, id_C)
);

Dan aku ingin (id_A, id_B, id_C) menjadi berbeda dalam situasi apa pun. Jadi, dua sisipan berikut harus menghasilkan kesalahan:

INSERT INTO my_table VALUES (1, 1, 2, NULL);
INSERT INTO my_table VALUES (2, 1, 2, NULL);

Tetapi tidak berlaku seperti yang diharapkan karena menurut dokumentasi, dua nilai NULL tidak dibandingkan satu sama lain, sehingga kedua sisipan lulus tanpa kesalahan.

Bagaimana saya bisa menjamin kendala unik saya bahkan jika id_C dapat berupa NULL dalam kasus ini? Sebenarnya, pertanyaan sebenarnya adalah: dapatkah saya menjamin keunikan seperti ini di "pure sql" atau apakah saya harus mengimplementasikannya pada level yang lebih tinggi (Java dalam kasus saya)?

102
Manuel Leduc

Anda dapat melakukannya di SQL murni . Buat sebagian indeks unik selain itu dengan yang Anda miliki:

CREATE UNIQUE INDEX ab_c_null_idx ON my_table (id_A, id_B) WHERE id_C IS NULL;

Dengan cara ini Anda bisa masuk untuk (a, b, c) di meja Anda:

(1, 2, 1)
(1, 2, 2)
(1, 2, NULL)

Tapi tidak ada yang kedua kalinya.

Atau gunakan dua sebagian UNIQUE indeks dan tidak ada indeks lengkap (atau kendala). Solusi terbaik tergantung pada detail kebutuhan Anda. Membandingkan:

Meskipun ini elegan dan efisien untuk satu kolom yang dapat dibatalkan dalam indeks UNIQUE, ini cepat hilang untuk lebih banyak. Membahas ini - dan bagaimana menggunakan UPSERT dengan indeks parsial:

Selain itu

Tidak digunakan untuk pengidentifikasi kasus campuran tanpa tanda kutip ganda di PostgreSQL.

Anda mungkin menganggap --- serial kolom sebagai kunci utama atau IDENTITY kolom di Postgres 10 atau kemudian. Terkait:

Begitu:

CREATE TABLE my_table (
   my_table_id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY  -- for pg 10+
-- my_table_id bigserial PRIMARY KEY  -- for pg 9.6 or older
 , id_a int8 NOT NULL
 , id_b int8 NOT NULL
 , id_c int8
 , CONSTRAINT u_constraint UNIQUE (id_a, id_b, id_c)
);

Jika Anda tidak mengharapkan lebih dari 2 miliar baris (> 2147483647) selama umur tabel Anda (termasuk baris limbah dan yang dihapus), pertimbangkan integer (4 byte) alih-alih bigint (8 byte).

102

Saya memiliki masalah yang sama dan saya menemukan cara lain untuk memiliki NULL yang unik ke dalam tabel.

CREATE UNIQUE INDEX index_name ON table_name( COALESCE( foreign_key_field, -1) )

Dalam kasus saya, bidang foreign_key_field adalah bilangan bulat positif dan tidak akan pernah menjadi -1.

Jadi, untuk menjawab Manual Leduc, solusi lain bisa jadi

CREATE UNIQUE INDEX  u_constrainte (COALESCE(id_a, -1), COALESCE(id_b,-1),COALESCE(id_c, -1) )

Saya berasumsi bahwa id tidak akan -1.

Apa keuntungan membuat indeks parsial?
Jika Anda tidak memiliki klausa NOT NULL, id_a, id_b dan id_c dapat NULL bersama hanya sekali.
Dengan indeks parsial, 3 bidang bisa NULL lebih dari sekali.

12
Luc M

Null dapat berarti bahwa nilai tidak diketahui untuk baris tersebut saat ini tetapi akan ditambahkan, bila diketahui, di masa mendatang (contoh FinishDate untuk running Project) atau bahwa tidak ada nilai yang dapat diterapkan untuk baris itu (contoh EscapeVelocity untuk lubang hitam Star).

Menurut pendapat saya, biasanya lebih baik untuk menormalkan tabel dengan menghilangkan semua Nulls.

Dalam kasus Anda, Anda ingin mengizinkan NULLs di kolom Anda, namun Anda hanya menginginkan satu NULL diizinkan. Mengapa? Hubungan macam apa ini di antara kedua tabel?

Mungkin Anda bisa mengubah kolom ke NOT NULL dan simpan, alih-alih NULL, nilai khusus (seperti -1) yang diketahui tidak pernah muncul. Ini akan menyelesaikan masalah kendala keunikan (tetapi mungkin memiliki efek samping lain yang mungkin tidak diinginkan. Misalnya, menggunakan -1 yang berarti "tidak dikenal/tidak berlaku" akan mencondongkan perhitungan jumlah atau rata-rata apa pun pada kolom. Atau semua perhitungan seperti itu harus memperhitungkan nilai khusus dan mengabaikannya.)

8
ypercubeᵀᴹ