Bileşik birincil anahtarlarda boş değer atanabilir sütunlarda sorun nedir?


154

ORACLE, birincil anahtarı içeren sütunların hiçbirinde NULL değerlere izin vermez. Görünüşe göre aynı durum diğer "işletme düzeyindeki" sistemlerin çoğu için de geçerli.

Aynı zamanda, çoğu sistem null yapılabilir sütunlarda benzersiz kısıtlamalara da izin verir .

Neden benzersiz kısıtlamaların NULL'leri olabilir, ancak birincil anahtarlar olamaz? Bunun temel bir mantıksal nedeni var mı, yoksa bu daha çok teknik bir sınırlama mı?


Yanıtlar:


223

Birincil anahtarlar, satırları benzersiz şekilde tanımlamak içindir. Bu, bir anahtarın tüm parçalarını girişle karşılaştırarak yapılır.

Tanım başına NULL, başarılı bir karşılaştırmanın parçası olamaz. Kendisiyle ( NULL = NULL) bir karşılaştırma bile başarısız olacaktır. Bu, NULL içeren bir anahtarın çalışmayacağı anlamına gelir.

Ek olarak, isteğe bağlı bir ilişkiyi işaretlemek için yabancı anahtarda NULL'a izin verilir. (*) PK'de de buna izin vermek bunu bozacaktır.


(*) Bir uyarı: Null yapılabilir yabancı anahtarlara sahip olmak temiz ilişkisel veritabanı tasarımı değildir.

İki varlık varsa Ave isteğe bağlı olarak ilişkilendirilebilecek Byerler varsa , temiz çözüm bir çözüm tablosu oluşturmaktır (diyelim ). Bu tablo bağlayacak ile : Orada ise olan bir ilişki var ise o zaman, bir kayıt içerecektir değil öyle değil mi o zaman.ABABAB


5
Kabul edilen cevabı buna değiştirdim. Oylara bakılırsa, bu cevap daha fazla insan için en açık olanıdır. Hala Tony Andrews'un cevabının bu tasarımın arkasındaki amacı daha iyi açıkladığını hissediyorum ; bir de kontrol et!
Roman Starkov

2
S: Satır eksikliği yerine NULL FK'yi ne zaman istiyorsunuz? C: Yalnızca optimizasyon için denormalize edilmiş bir şema sürümünde. Önemsiz olmayan şemalarda, bunun gibi normalleştirilmemiş sorunlar, yeni özellikler gerektiğinde sorunlara neden olabilir. otoh, web tasarım kalabalığı umursamıyor. Kulağa iyi bir tasarım fikri gibi görünmek yerine, en azından bu konuda bir uyarı notu ekleyeceğim.
zxq9

4
"Boş değer atanabilir yabancı anahtarlara sahip olmak temiz ilişkisel veritabanı tasarımı değildir." - boş olmayan bir veritabanı tasarımı (Altıncı normal biçim) her zaman karmaşıklık katar, kazanılan alan tasarrufu, bu kazanımları gerçekleştirmek için gereken ekstra programcı çalışması tarafından genellikle ağır basar.
Dai

1
Ya bir ABC çözünürlük tablosu ise? isteğe bağlı C
Bart Calixto ile

1
Bu gerçekten hiçbir şeyi açıklamadığı için "standart bunu yasakladığı için" yazmaktan kaçınmaya çalıştım.
Tomalak

63

Birincil anahtar, bir tablodaki her satır için benzersiz bir tanımlayıcı tanımlar : bir tablonun birincil anahtarı olduğunda, ondan herhangi bir satırı seçmek için garantili bir yolunuz olur.

Benzersiz bir kısıtlamanın her satırı tanımlaması gerekmez; sadece bir satırın sütunlarında değerler varsa , o zaman bunların benzersiz olması gerektiğini belirtir . Bu, her satırı benzersiz olarak tanımlamak için yeterli değildir , bu da birincil anahtarın yapması gereken şeydir.


10
Sql Server'da null yapılabilir bir sütuna sahip benzersiz bir kısıtlama, bu sütundaki 'null' değerine yalnızca bir kez izin verir (kısıtlamanın diğer sütunları için aynı değerler verilir). Dolayısıyla, böyle benzersiz bir kısıtlama, esasen null yapılabilir bir sütuna sahip bir pk gibi davranır.
Gerard

Oracle (11.2) için de aynısını onaylıyorum
Alexander Malakhov

2
Oracle'da (SQL Server hakkında bilmiyorum), tablo benzersiz bir kısıtlamadaki tüm sütunların boş olduğu birçok satır içerebilir . Ancak, benzersiz kısıtlamadaki bazı sütun (lar) boş değilse ve bazıları boşsa, benzersizlik uygulanır.
Tony Andrews

Bu, UNIQUE bileşimi için nasıl geçerlidir?
ölçülü yük

1
@Dims SQL veritabanlarındaki hemen hemen her şeyde olduğu gibi "uygulamaya bağlıdır". Çoğu dbs'de bir "birincil anahtar" aslında altında bir EŞSİZ kısıtlamadır. "Birincil anahtar" fikri, EŞSİZ kavramından daha özel veya güçlü değildir. Gerçek fark, bir tablonun EŞSİZ garanti edilebilecek iki bağımsız yönüne sahipseniz, tanım gereği normalleştirilmiş bir veritabanına sahip olmadığınızdır (aynı tabloda iki tür veri depoluyorsunuz).
zxq9

48

Temel olarak konuşursak, çok sütunlu bir birincil anahtardaki NULL ile hiçbir şey yanlış değildir. Ancak bir tanesine sahip olmak, tasarımcının muhtemelen istemediği sonuçlara sahiptir, bu yüzden bunu denediğinizde birçok sistem hata verir.

Bir dizi alan olarak depolanan modül / paket sürümlerini düşünün:

CREATE TABLE module
  (name        varchar(20) PRIMARY KEY,
   description text DEFAULT '' NOT NULL);

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20),
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

Birincil anahtarın ilk 5 öğesi, bir yayın sürümünün düzenli olarak tanımlanmış parçalarıdır, ancak bazı paketler genellikle tamsayı olmayan özelleştirilmiş bir uzantıya sahiptir ("rc-foo" veya "vanilya" veya "beta" gibi veya başka biri ne için olursa olsun) kime dört alanları) hayal olabilir yetersizdir. Bir paketin bir uzantısı yoksa yukarıdaki modelde NULL olur ve işleri böyle bırakarak herhangi bir zarar verilmez.

Ama ne olduğunu bir BOŞ? Bilgi eksikliğini , bilinmeyeni temsil etmesi gerekiyordu . Bununla birlikte, belki bu daha mantıklıdır:

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20) DEFAULT '' NOT NULL,
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

Bu versiyonda, başlığın "ext" kısmı NULL DEĞİLDİR, ancak öntanımlı olarak boş bir dizgedir - anlamsal olarak (ve pratik olarak) NULL'dan farklıdır. NULL bilinmeyen bir dizge iken boş dizge "mevcut olmayan bir şeyin" kasıtlı bir kaydıdır. Diğer bir deyişle, "boş" ve "boş" farklı şeylerdir. "Burada bir değerim yok" ile "Buradaki değerin ne olduğunu bilmiyorum" arasındaki fark bu.

Sürüm uzantısı olmayan bir paketi kaydettiğinizde , bir uzantıdan yoksun olduğunu bilirsiniz , bu nedenle boş bir dize aslında doğru değerdir. Bir NULL, yalnızca bir uzantısı olup olmadığını bilmiyorsanız veya ne olduğunu bildiğiniz halde ne olduğunu bilmiyorsanız doğru olacaktır. Dize değerlerinin norm olduğu sistemlerde bu durumla başa çıkmak daha kolaydır, çünkü 0 veya 1 eklemek dışında "boş bir tamsayıyı" temsil etmenin yolu yoktur, bu daha sonra yapılan herhangi bir karşılaştırmada toplanır (ki kendi sonuçları) *.

Bu arada, Postgres'te her iki yol da geçerlidir ("kurumsal" RDMBS'leri tartıştığımız için), ancak karışıma bir NULL attığınızda karşılaştırma sonuçları biraz değişebilir - çünkü NULL == "bilmiyorum" yani hepsi NULL içeren bir karşılaştırmanın sonuçları, bilinmeyen bir şeyi bilemeyeceğiniz için NULL olur. TEHLİKE! Bunu dikkatlice düşünün: Bu, NULL karşılaştırma sonuçlarının bir dizi karşılaştırma yoluyla yayıldığı anlamına gelir . Bu, sıralarken, karşılaştırırken vb. İnce hataların kaynağı olabilir.

Postgres, yetişkin olduğunuzu varsayar ve bu kararı kendiniz verebilir. Oracle ve DB2, aptalca bir şey yaptığınızın farkında olmadığınızı varsayar ve bir hata verir. Bu genellikle doğru olan şeydir, ancak her zaman değil - aslında bilmiyor olabilirsiniz ve bazı durumlarda bir NULL'a sahip olabilirsiniz ve bu nedenle, anlamlı karşılaştırmaların imkansız olduğu bilinmeyen bir öğeyle bir satır bırakmak doğru davranıştır.

Her durumda, tüm şema boyunca izin verdiğiniz NULL alanların sayısını ortadan kaldırmaya çalışmalısınız ve bir birincil anahtarın parçası olan alanlar söz konusu olduğunda bunu iki katına çıkarmalısınız. Vakaların büyük çoğunluğunda, NULL sütunlarının varlığı, normalize edilmemiş (kasıtlı olarak normalleştirilmesinin aksine) şema tasarımının bir göstergesidir ve kabul edilmeden önce çok düşünülmesi gerekir.

[* NOT: Tamsayıların birleşimi olan özel bir tür ve "bilinmeyen" yerine anlamsal olarak "boş" anlamına gelen "alt" bir tür oluşturmak mümkündür. Ne yazık ki bu, karşılaştırma işlemlerinde biraz karmaşıklık getirir ve genellikle gerçekten doğru tipte olmak pratikte çabaya değmez, NULLçünkü ilk başta birçok değere izin verilmemelidir . Bununla birlikte, RDBMS'lerin "değer yok" anlamını "bilinmeyen değer" ile rasgele bir şekilde karıştırma alışkanlığını önlemek için varsayılan bir BOTTOMtür içermesi harika olurdu NULL. ]


6
Bu ÇOK GÜZEL bir cevaptır ve NULL değerleri hakkında çok şey açıklar ve birçok durumdaki çıkarımları. Siz bayım, şimdi saygı duyuyorum! Üniversitede bile veri tabanları içindeki NULL değerleri hakkında bu kadar iyi bir açıklamam var. Teşekkür ederim!

Bu cevabın ana fikrini destekliyorum. Ancak 'bilgi eksikliğini, bilinmeyen', 'anlamsal olarak (ve pratik olarak) NULL'dan farklı', 'NULL bilinmeyen', 'boş bir dizge, "mevcut olmayan bir şeyin kasıtlı bir kaydıdır. "',' NULL ==" bilmiyorum "', vb. Belirsiz ve yanıltıcıdır ve gerçekten sadece NULL veya herhangi bir değerin ne kadar kullanılabildiğini veya kullanılmasının amaçlandığına dair eksik ifadeler için anımsatıcılar - yazının geri kalanına göre . (SQL NULL özelliklerinin (kötü) tasarımına ilham vermek dahil.) Hiçbir şeyi haklı çıkarmaz veya açıklamazlar; açıklanmalı ve çürütülmelidir.
philipxy

22

NULL == NULL -> yanlış (en azından DBMS'lerde)

Bu nedenle, gerçek değerlere sahip ek sütunlarla bile NULL değeri kullanarak herhangi bir ilişkiyi alamazsınız.


1
Bu en iyi cevap gibi görünüyor, ancak yine de birincil anahtar oluşturmada bunun neden yasaklandığını anlamıyorum. Bu sadece bir geri getirme problemiyse, where pk_1 = 'a' and pk_2 = 'b'normal değerlerle kullanabilir ve where pk_1 is null and pk_2 = 'b'boş değerler olduğunda geçiş yapabilirsiniz .
EoghanM

Ya da daha güvenilir bir şekilde, where (a.pk1 = b.pk1 or (a.pk1 is null and b.pk1 is null)) and (a.pk2 = b.pk2 or (a.pk2 is null and b.pk2 is null))/
Jordan Rieger

8
Yanlış cevap. NULL == NULL -> BİLİNMİYOR. Yanlış değil. Buradaki sorun, testin sonucunun BİLİNMİYOR olması durumunda bir kısıtlamanın ihlal edilmiş olarak kabul edilmemesidir. Bu genellikle karşılaştırmanın yanlış çıkmasına neden oluyor gibi görünmesine neden olur , ancak gerçekten değildir.
Erwin Smout

5

Tony Andrews'un cevabı makul. Ancak gerçek cevap, bunun ilişkisel veritabanı topluluğu tarafından kullanılan bir kural olduğu ve bir zorunluluk olmadığıdır. Belki bu iyi bir kongredir, belki de değildir.

Herhangi bir şeyi NULL ile karşılaştırmak UNKNOWN (3. gerçek değeri) ile sonuçlanır. Boşluklarla önerildiği gibi, eşitlikle ilgili tüm geleneksel bilgelik pencereden dışarı çıkar. İlk bakışta böyle görünüyor.

Ancak bunun zorunlu olduğunu düşünmüyorum ve SQL veritabanları bile NULL'un karşılaştırma için tüm olasılıkları yok ettiğini düşünmüyor.

Veritabanınızda SELECT * FROM VALUES (NULL) UNION SELECT * FROM VALUES (NULL) sorgusunu çalıştırın

Gördüğünüz şey, NULL değerine sahip bir özniteliğe sahip yalnızca bir demettir. Böylece sendika burada iki NULL değerini eşit olarak kabul etti.

3 bileşeni olan bir bileşik anahtarı 3 öznitelik (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 VE 3 = 3 VE NULL = NULL olan bir tuple ile karşılaştırırken Bunun sonucu BİLİNMİYOR .

Ancak yeni bir tür karşılaştırma operatörü tanımlayabiliriz, örneğin. ==. X == Y <=> X = Y VEYA (X NULL VE Y NULL)

Bu tür bir eşitlik operatörüne sahip olmak, boş bileşenli bileşik anahtarları veya boş değerli bileşik olmayan anahtarları sorunsuz hale getirir.


1
Hayır, BİRLİĞİ, iki BOŞ'u farklı olmayan olarak kabul etti. Bu "eşit" ile aynı şey değil. Bunun yerine UNION ALL'u deneyin ve iki satır alacaksınız. "Yeni tür karşılaştırma operatörü" ne gelince, SQL zaten buna sahiptir. FARKLI DEĞİLDİR. Ancak bu tek başına yeterli değildir. Bunu NATURAL JOIN gibi SQL yapılarında veya bir yabancı anahtarın REFERENCES yan tümcesinde kullanmak, bu yapılarda ek seçenekler gerektirecektir.
Erwin Smout

Aha, Erwin Smout. Sizinle bu forumda da tanışmak gerçekten büyük bir zevk! SQL'in "IS NOT DISTINCT FROM" un farkında değildim. Çok ilginç! Ama öyle görünüyor ki uydurma == operatörümle kastettiğim tam olarak buydu. Bana neden "bu tek başına yeterli değil" dediğinizi açıklar mısınız?
Rami Ojares

REFERENCES cümlesi, tanımı gereği eşitlik üzerine kuruludur. İlgili öznitelik değerlerinin (daha katı olan) EŞİT OLMADIĞINI (daha katı) EŞİT OLMADIĞINI temel alan bir alt tuple / satırı üst tuple / satırla eşleştiren bir tür REFERANSLAR, bu seçeneği belirleme yeteneğini gerektirir, ancak sözdizimi gerektirmez izin ver. NATURAL JOIN için aynen.
Erwin Smout

Bir yabancı anahtarın çalışması için, başvurulan anahtar benzersiz olmalıdır (yani, tüm değerler farklı olmalıdır). Bu, tek bir boş değere sahip olabileceği anlamına gelir. REFERENCES NOT DISTINCT operatörü ile tanımlanacaksa, tüm boş değerler bu tek boşluğa başvurabilir. Bunun daha iyi olacağını düşünüyorum (daha kullanışlı olması anlamında). JOIN'ler ile (hem dış hem de iç) katı eşitlerin daha iyi olduğunu düşünüyorum çünkü sol taraftaki boş değerler sağ taraftaki tüm boşlarla eşleştiğinde "BOŞ MAÇLAR" çarpılır.
Rami Ojares

1

Hala bunun teknikliğin getirdiği temel / işlevsel bir kusur olduğuna inanıyorum. Bir müşteriyi tanımlayabileceğiniz isteğe bağlı bir alanınız varsa, şimdi boş bir değeri kırmanız gerekir, çünkü NULL! = NULL, özellikle zarif değil, ancak bu bir "endüstri standardı"

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.