PostgreSQL çok sütunlu benzersiz kısıtlama ve NULL değerler


94

Aşağıdaki gibi bir tablo var:

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

Ve (id_A, id_B, id_C)her durumda farklı olmak istiyorum . Bu nedenle, aşağıdaki iki ek bir hatayla sonuçlanmalıdır:

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

Ancak, beklendiği gibi davranmaz, çünkü belgelere göre, iki NULLdeğer birbiriyle karşılaştırılmaz, bu nedenle her iki uç da hatasız geçer.

Bu durumda olsa bile id_C, benzersiz kısıtlamamı nasıl garanti edebilirim NULL? Aslında asıl soru şudur: "saf sql" de bu tür benzersizliği garanti edebilir miyim veya daha yüksek bir düzeyde uygulamak zorunda mıyım (benim durumumda java)?


Yani, değerlere sahip söylemek (1,2,1)ve (1,2,2)içinde (A,B,C)sütunlar. Eklenmeye (1,2,NULL)izin verilmeli mi veya edilmemeli mi?
ypercubeᵀᴹ

A ve B boş olamaz ancak C boş olabilir veya herhangi bir pozitif tamsayı değeri olabilir. Yani (1,2,3) ve (2,4, null) geçerlidir, ancak (null, 2,3) veya (1, null, 4) geçersizdir. Ve [(1,2, null), (1,2,3)] benzersiz kısıtlamayı bozmaz, ancak [(1,2, null), (1,2, null)] onu kırmalıdır.
Manuel Leduc,

2
Bu sütunlarda asla görünmeyecek herhangi bir değer var mı (negatif değerler gibi?)
a_horse_with_no_name 27:11

Kısıtlamalarınızı pg olarak etiketlemeniz gerekmez. Otomatik olarak bir isim oluşturur. Sadece FYI.
Evan Carroll

Yanıtlar:


94

Bunu saf SQL'de yapabilirsiniz . Sahip olduğunuza ek olarak kısmi bir benzersiz dizin oluşturun :

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

Bu şekilde (a, b, c)masanıza girebilirsiniz :

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

Fakat bunların hiçbiri ikinci kez değil.

Veya iki kısmi UNIQUEindeks kullanın ve tam bir indeks (veya kısıtlama) kullanmayın. En iyi çözüm gereksinimlerinizin ayrıntılarına bağlıdır. Karşılaştırmak:

Bu şık, UNIQUEendekste tek bir null sütunu için verimli olmasına rağmen , daha fazlası için hızla elden çıkar. Bunu tartışmak - ve UPSERT'yi kısmi indekslerle kullanmak:

asides

PostgreSQL'de çift ​​tırnak işareti içermeyen karışık vaka tanımlayıcıları için kullanılamaz

Sen belki bir düşünün serialsütunu birincil anahtar olarak veya bir IDENTITYsütun 10 veya daha sonraki Postgres. İlgili:

Yani:

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

Masanızın kullanım ömrü boyunca 2 milyardan fazla satır (> 2147483647) beklemiyorsanız (atık ve silinmiş satırlar dahil), (8 bayt) integeryerine (4 bayt) düşünün bigint.


1
Dokümanlar bu yöntemi savunur, Benzersiz bir kısıtlama eklemek, kısıtlamada listelenen sütunlar veya sütunlar grubunda otomatik olarak benzersiz bir B-ağacı dizini oluşturur. Yalnızca bazı satırları kapsayan benzersiz bir kısıtlama, benzersiz bir kısıtlama olarak yazılamaz, ancak benzersiz bir kısmi indeks oluşturarak böyle bir kısıtlamayı uygulamak mümkündür.
Evan Carroll

12

Aynı problemi yaşadım ve benzersiz NULL'ın masaya girmesinin başka bir yolunu buldum.

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

Benim durumumda, alan foreign_key_fieldpozitif bir tamsayıdır ve asla -1 olmaz.

Bu yüzden, Manuel Leduc'a cevap vermek için başka bir çözüm olabilir.

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

Kimliğin -1 olmayacağını varsayıyorum.

Kısmi bir dizin oluşturmanın avantajı nedir?
NOT NULL yantümcesinin olmadığı durumlarda id_a, id_bve bir id_ckez NULL olabilir.
Kısmi bir dizinde, 3 alan bir defadan fazla NULL olabilir.


3
> Kısmi endeks oluşturmanın avantajı nedir? Bunu yapma biçiminiz COALESCEkopyaları kısıtlamada etkili olabilir, ancak dizin, muhtemelen sorgu ifadeleriyle eşleşmeyen bir ifade dizini olarak sorgulamada çok yararlı olmaz. Yani, sen SELECT COALESCE(col, -1) ...endekse vurmazsan tabii .
Bo Jeanes,

@BoJeanes Dizin, bir performans sorunu için oluşturulmadı. İş gerekliliklerini yerine getirmek için yaratılmıştır.
Luc M

8

Boş (Null), o anda o satır için bilinmeyen bir değer anlamına gelebilir, ancak bilinirse ileride ( FinishDatebir koşu için örnek Project) eklenemeyeceği veya söz konusu satır için hiçbir değer uygulanamayacağı ( EscapeVelocitybir karadelik için) anlamına gelebilir Star.

Kanımca, tüm Boşları kaldırarak tabloları normalleştirmek daha iyi olur.

Senin durumunda, NULLssütununa izin vermek istiyorsun , ama sadece bir tanesine NULLizin vermek istiyorsun . Neden? Bu iki tablo arasında nasıl bir ilişki var?

Belki de hiçbir zaman görünmeyeceği bilinen özel bir değer (gibi ) NOT NULLyerine sütunu değiştirip saklayabilirsiniz . Bu, benzersiz kısıtlama problemini çözecektir (ancak diğer olası istenmeyen yan etkileri de olabilir. Örneğin, "bilinmeyen / uygulanmayan" anlamına gelmesi, sütundaki herhangi bir toplamı veya ortalama hesaplamaları çarpıtır veya tüm bu hesaplamaları almak zorunda kalır. özel değeri hesaba katarak dikkate almayın.)NULL-1-1


2
Benim durumumda NULL gerçekten NULL (id_C, exemple için table_c için yabancı bir anahtardır, yani -1 değerine sahip olamaz), onların "my_table" ve "table_c" arasında bir ilişki olmadığı anlamına gelir. Bu yüzden işlevsel bir işareti vardır. Bu arada [(1, 1,1, null), (2, 1,2, null), (3,2,4, null)] geçerli bir eklenen veri listesidir.
Manuel Leduc

1
Gerçekten de SQL'de kullanıldığı gibi bir Null değil çünkü tüm satırlarda sadece birini istiyorsunuz. Veritabanı şemasını, table_c'ye -1 ekleyerek veya başka bir tablo ekleyerek (bu, table_c alt tipine süper tip olacaktır) değiştirebilir.
ypercubeᵀᴹ

3
Sadece @Manuel'e bu cevaptaki boşlar hakkındaki görüşün evrensel olarak tutulmadığını ve çok tartışıldığını belirtmek isterim. Benim gibi çoğu kişi, null'un istediğiniz herhangi bir amaç için kullanılabileceğini düşünüyor (ancak her alan için yalnızca bir şey ifade etmeli ve muhtemelen alan adına veya bir sütuna yorumunda belgelenmelidir)
Jack Douglas

1
Sütun bir YABANCI ANAHTAR ise aptal değeri kullanamazsınız.
Luc M,

1
+1 Yanınızdayım: eğer bazı sütun kombinasyonlarının benzersiz olmasını istersek, bu sütun kombinasyonunun PK olduğu bir varlığı göz önünde bulundurmanız gerekir. OP'lerin veritabanı şeması muhtemelen bir üst tabloya ve bir alt tabloya değişmelidir.
AK
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.