Apa yang salah dengan kolom nullable di kunci primer komposit?

149

ORACLE tidak mengizinkan nilai NULL di salah satu kolom yang terdiri dari kunci utama. Tampaknya hal yang sama berlaku untuk sebagian besar sistem "tingkat perusahaan" lainnya.

Pada saat yang sama, sebagian besar sistem juga memungkinkan hambatan unik pada kolom yang dapat dibatalkan.

Mengapa kendala unik dapat memiliki NULL tetapi kunci primer tidak bisa? Apakah ada alasan logis mendasar untuk ini, atau ini lebih merupakan batasan teknis?

Roman Starkov
sumber

Jawaban:

216

Kunci primer adalah untuk baris pengidentifikasi unik. Ini dilakukan dengan membandingkan semua bagian kunci dengan input.

Per definisi, NULL tidak dapat menjadi bagian dari perbandingan yang berhasil. Bahkan perbandingan dengan dirinya sendiri ( NULL = NULL) akan gagal. Ini berarti kunci yang berisi NULL tidak akan berfungsi.

Selain itu, NULL diizinkan dalam kunci asing, untuk menandai hubungan opsional. (*) Membiarkannya di PK juga akan merusak ini.


(*) Kata peringatan: Memiliki kunci asing nullable tidak bersih desain basis data relasional.

Jika ada dua entitas Adan di Bmana Asecara opsional dapat dikaitkan B, solusi bersih adalah membuat tabel resolusi (katakanlah AB). Tabel yang akan menghubungkan Adengan B: Jika ada adalah hubungan maka akan berisi catatan, jika ada tidak maka tidak akan.

Tomalak
sumber
5
Saya telah mengubah jawaban yang diterima untuk yang ini. Berdasarkan penilaian, jawaban ini paling jelas bagi lebih banyak orang. Saya masih merasa bahwa jawaban oleh Tony Andrews menjelaskan maksud di balik desain ini dengan lebih baik; lakukan check it out juga!
Roman Starkov
2
T: Kapan Anda menginginkan NULL FK alih-alih kekurangan baris? A: Hanya dalam versi skema yang didenormalisasi untuk optimisasi. Dalam skema non-sepele masalah yang tidak normal seperti ini dapat menyebabkan masalah setiap kali fitur baru diperlukan. Ya, kerumunan desain web tidak peduli. Saya setidaknya akan menambahkan catatan tentang hal ini daripada membuatnya terdengar seperti ide desain yang bagus.
zxq9
3
"Memiliki kunci asing nullable tidak bersih desain basis data relasional." - desain basis data bebas-nol (bentuk normal keenam) selalu menambah kompleksitas, penghematan ruang yang diperoleh sering kali lebih berat daripada pekerjaan programmer tambahan yang diperlukan untuk merealisasikan keuntungan tersebut.
Dai
1
bagaimana jika ini adalah tabel resolusi ABC? dengan opsional C
Bart Calixto
1
Saya mencoba untuk menghindari menulis "karena standar melarangnya", karena ini tidak menjelaskan apa-apa.
Tomalak
62

Kunci utama mendefinisikan pengidentifikasi unik untuk setiap baris dalam sebuah tabel: ketika sebuah tabel memiliki kunci utama, Anda memiliki cara yang dijamin untuk memilih baris mana pun darinya.

Batasan unik tidak selalu mengidentifikasi setiap baris; itu hanya menentukan bahwa jika suatu baris memiliki nilai dalam kolomnya, maka mereka harus unik. Ini tidak cukup untuk mengidentifikasi secara unik setiap baris, yang harus dilakukan oleh kunci utama.

Tony Andrews
sumber
10
Dalam Sql Server kendala unik yang memiliki kolom nullable, memungkinkan nilai 'null' di kolom itu hanya sekali (diberi nilai identik untuk kolom lain dari kendala). Jadi kendala unik seperti itu pada dasarnya berperilaku seperti pk dengan kolom nullable.
Gerard
Saya mengkonfirmasi hal yang sama untuk Oracle (11.2)
Alexander Malakhov
2
Di Oracle (saya tidak tahu tentang SQL Server), tabel dapat berisi banyak baris di mana semua kolom dalam batasan unik adalah nol. Namun, jika beberapa kolom dalam batasan unik tidak nol dan ada yang nol maka keunikan ditegakkan.
Tony Andrews
Bagaimana ini berlaku untuk UNIK komposit?
Dims
1
@Dims Seperti hampir semua hal lain dalam database SQL "itu tergantung pada implementasinya". Dalam kebanyakan dbs, "kunci utama" sebenarnya merupakan batasan UNIK di bawahnya. Gagasan "kunci utama" sebenarnya tidak lebih istimewa atau kuat daripada konsep UNIQUE. Perbedaan sebenarnya adalah bahwa jika Anda memiliki dua aspek independen dari sebuah tabel yang dapat dijamin UNIK maka Anda tidak memiliki database yang dinormalisasi menurut definisi (Anda menyimpan dua jenis data dalam tabel yang sama).
zxq9
46

Secara mendasar tidak ada yang salah dengan NULL dalam kunci utama multi-kolom. Tetapi memiliki satu implikasi yang kemungkinan tidak diinginkan oleh perancang, itulah sebabnya banyak sistem membuat kesalahan ketika Anda mencoba ini.

Pertimbangkan kasus versi modul / paket yang disimpan sebagai serangkaian bidang:

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

5 elemen pertama dari kunci utama secara teratur didefinisikan sebagai bagian dari versi rilis, tetapi beberapa paket memiliki ekstensi khusus yang biasanya bukan bilangan bulat (seperti "rc-foo" atau "vanilla" atau "beta" atau apa pun yang dilakukan seseorang untuk yang empat bidang tidak mencukupi mungkin bermimpi). Jika sebuah paket tidak memiliki ekstensi, maka itu NULL dalam model di atas, dan tidak ada salahnya dilakukan dengan meninggalkan hal-hal seperti itu.

Tapi apa itu NULL? Seharusnya mewakili kurangnya informasi, tidak diketahui. Yang mengatakan, mungkin ini lebih masuk akal:

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

Dalam versi ini bagian "ext" dari tuple BUKAN NULL tetapi default ke string kosong - yang secara semantik (dan praktis) berbeda dari NULL. NULL adalah suatu yang tidak diketahui, sedangkan string kosong adalah catatan yang disengaja dari "sesuatu yang tidak ada". Dengan kata lain, "kosong" dan "nol" adalah hal yang berbeda. Perbedaan antara "Saya tidak punya nilai di sini" dan "Saya tidak tahu apa nilai di sini."

Ketika Anda mendaftarkan paket yang tidak memiliki ekstensi versi, Anda tahu itu tidak memiliki ekstensi, jadi string kosong sebenarnya adalah nilai yang benar. NULL hanya akan benar jika Anda tidak tahu apakah ekstensi itu atau tidak, atau Anda tahu itu ekstensi tetapi tidak tahu apa itu ekstensi. Situasi ini lebih mudah untuk ditangani dalam sistem di mana nilai string adalah norma, karena tidak ada cara untuk mewakili "integer kosong" selain memasukkan 0 atau 1, yang akhirnya akan digulung dalam perbandingan apa pun yang dibuat kemudian (yang memiliki implikasinya sendiri) *.

Kebetulan, kedua cara ini valid di Postgres (karena kita sedang membahas "perusahaan" RDMBSs), tetapi hasil perbandingan dapat sedikit berbeda ketika Anda melempar NULL ke dalam campuran - karena NULL == "tidak tahu" jadi semua hasil perbandingan yang melibatkan NULL akhirnya menjadi NULL karena Anda tidak dapat mengetahui sesuatu yang tidak dikenal. BAHAYA! Pikirkan baik-baik tentang itu: ini berarti bahwa hasil perbandingan NULL menyebar melalui serangkaian perbandingan. Ini bisa menjadi sumber bug halus saat menyortir, membandingkan, dll.

Postgres mengasumsikan Anda sudah dewasa dan dapat mengambil keputusan sendiri. Oracle dan DB2 menganggap Anda tidak menyadari bahwa Anda melakukan sesuatu yang konyol dan membuat kesalahan. Ini biasanya hal yang benar, tetapi tidak selalu - Anda mungkin sebenarnya tidak tahu dan memiliki NULL dalam beberapa kasus dan karenanya meninggalkan baris dengan elemen yang tidak diketahui yang tidak mungkin dilakukan perbandingan yang berarti adalah perilaku yang benar.

Dalam kasus apa pun Anda harus berusaha untuk menghilangkan jumlah bidang NULL yang Anda izinkan di seluruh skema dan dua kali lipat ketika menyangkut bidang yang merupakan bagian dari kunci utama. Dalam sebagian besar kasus, keberadaan kolom NULL merupakan indikasi rancangan skema yang tidak dinormalisasi (berlawanan dengan yang tidak dinormalisasi dengan sengaja) dan harus dipikirkan dengan keras sebelum diterima.

[* CATATAN: Dimungkinkan untuk membuat tipe khusus yang merupakan gabungan bilangan bulat dan tipe "bawah" yang secara semantik berarti "kosong" sebagai kebalikan dari "tidak dikenal". Sayangnya ini memperkenalkan sedikit kompleksitas dalam operasi perbandingan dan biasanya benar-benar mengetik benar tidak sepadan dengan usaha dalam praktik karena Anda tidak boleh diizinkan banyak NULLnilai sama sekali di tempat pertama. Yang mengatakan, akan luar biasa jika RDBMS akan memasukkan BOTTOMtipe standar selain NULLuntuk mencegah kebiasaan santai semantik semantik "tidak ada nilai" dengan "nilai tidak diketahui". ]

zxq9
sumber
5
Ini adalah jawaban SANGAT BAGUS dan menjelaskan banyak tentang nilai-nilai NULL dan implikasinya melalui banyak situasi. Anda, Tuan, sekarang hormati saya! Bahkan di perguruan tinggi pun saya mendapat penjelasan yang bagus tentang nilai-nilai NULL di dalam basis data. Terima kasih!
Saya mendukung gagasan utama dari jawaban ini. Tetapi menulis seperti 'seharusnya mewakili kurangnya informasi, tidak diketahui', 'semantik (dan praktis) berbeda dari NULL', 'A NULL adalah tidak diketahui', 'string kosong adalah catatan yang disengaja dari "sesuatu yang tidak hadir "',' NULL ==" tidak tahu "', dll tidak jelas & menyesatkan & benar-benar hanya mnemonik untuk pernyataan absen tentang bagaimana NULL atau nilai apa pun atau dapat atau dimaksudkan untuk digunakan - per sisa pos . (Termasuk dalam menginspirasi desain (buruk) fitur SQL NULL.) Mereka tidak membenarkan atau menjelaskan apa pun; mereka harus dijelaskan & dibantah.
philipxy
21

NULL == NULL -> false (setidaknya dalam DBMSs)

Jadi, Anda tidak akan dapat mengambil hubungan apa pun menggunakan nilai NULL bahkan dengan kolom tambahan dengan nilai nyata.

Cogsy
sumber
1
Ini terdengar seperti jawaban terbaik, tapi saya masih tidak mengerti mengapa ini dilarang pada pembuatan kunci primer. Jika ini hanya masalah pengambilan, Anda bisa menggunakan where pk_1 = 'a' and pk_2 = 'b'dengan nilai normal, dan beralih ke where pk_1 is null and pk_2 = 'b'ketika ada nol.
EoghanM
Atau bahkan lebih andal, 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
Jawaban yang salah. NULL == NULL -> UNKNOWN. Tidak salah. Tangkapannya adalah bahwa kendala tidak dianggap dilanggar jika hasil tes TIDAK DIKETAHUI. Ini sering membuatnya TAMPAK seolah-olah perbandingan menghasilkan palsu, tetapi sebenarnya tidak.
Erwin Smout
4

Jawaban oleh Tony Andrews adalah jawaban yang layak. Tetapi jawaban sebenarnya adalah bahwa ini telah menjadi konvensi yang digunakan oleh komunitas basis data relasional dan BUKAN suatu keharusan. Mungkin itu adalah konvensi yang bagus, mungkin juga tidak.

Membandingkan apa pun dengan NULL menghasilkan TIDAK DIKETAHUI (nilai kebenaran ke-3). Jadi seperti yang telah disarankan dengan nol semua kearifan tradisional tentang kesetaraan keluar jendela. Yah begitulah tampaknya pada pandangan pertama.

Tapi saya tidak berpikir ini perlu begitu dan bahkan database SQL tidak berpikir bahwa NULL menghancurkan semua kemungkinan untuk perbandingan.

Jalankan di basis data Anda kueri SELECT * FROM VALUES (NULL) UNION SELECT * FROM VALUES (NULL)

Yang Anda lihat hanyalah satu tuple dengan satu atribut yang memiliki nilai NULL. Jadi, serikat pekerja mengakui di sini dua nilai NULL sebagai sama.

Ketika membandingkan kunci komposit yang memiliki 3 komponen untuk tupel dengan 3 atribut (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 DAN 3 = 3 DAN NULL = NULL Hasil dari ini TIDAK DIKETAHUI .

Tapi kita bisa mendefinisikan operator perbandingan jenis baru misalnya. ==. X == Y <=> X = Y ATAU (X IS NULL DAN Y IS NULL)

Memiliki operator kesetaraan semacam ini akan membuat kunci komposit dengan komponen nol atau kunci non-komposit dengan nilai nol tidak bermasalah.

Rami Ojares
sumber
1
Tidak, UNION mengakui kedua NULL sebagai tidak berbeda. Yang tidak sama dengan "sama". Coba UNION ALL dan Anda akan mendapatkan dua baris. Dan untuk "operator perbandingan jenis baru", SQL sudah memilikinya. BUKANLAH DARI. Tetapi itu saja tidak cukup. Menggunakan ini dalam konstruksi SQL seperti NATURAL JOIN, atau klausa REFERENSI kunci asing, akan memerlukan opsi tambahan pada konstruksi tersebut.
Erwin Smout
Aha, Erwin Smout. Sungguh senang bertemu dengan Anda juga di forum ini! Saya tidak mengetahui SQL "IS NOT DISTINCT FROM". Sangat menarik! Tapi sepertinya itulah yang saya maksud dengan operator == buatan saya. Bisakah Anda menjelaskan kepada saya mengapa Anda mengatakan itu: "itu saja tidak cukup"?
Rami Ojares
Klausul REFERENSI didasarkan pada kesetaraan, menurut definisi. Semacam REFERENSI yang cocok dengan tupel anak / baris dengan tupel induk / baris, berdasarkan nilai atribut yang sesuai BUKAN berbeda, bukan (lebih ketat) EQUAL, akan memerlukan kemampuan untuk menentukan opsi ini, tetapi sintaksisnya tidak izinkan itu. Ditto untuk GABUNG ALAMI.
Erwin Smout
Agar kunci asing berfungsi, yang dirujuk harus unik (mis. Semua nilai harus berbeda). Yang berarti itu bisa memiliki nilai nol tunggal. Semua nilai nol kemudian dapat merujuk ke nol tunggal itu jika REFERENSI akan didefinisikan dengan operator NOT DISTINCT. Saya pikir itu akan lebih baik (dalam arti lebih bermanfaat). Dengan BERGABUNG (baik luar dan dalam) saya pikir sama dengan ketat lebih baik karena "NULL MATCHES" akan berlipat ganda ketika nol di sisi kiri akan cocok dengan semua nol di sisi kanan.
Rami Ojares
1

Saya masih percaya ini adalah kelemahan mendasar / fungsional yang disebabkan oleh masalah teknis. Jika Anda memiliki bidang opsional tempat Anda dapat mengidentifikasi pelanggan, kini Anda harus meretas nilai tiruan ke dalamnya, hanya karena NULL! = NULL, tidak terlalu elegan namun ini merupakan "standar industri"

Adriaan Davel
sumber