Mengapa batasan UNIK memungkinkan hanya satu NULL?

36

Secara teknis, NULL = NULL adalah False, dengan logika itu tidak ada NULL yang sama dengan NULL dan semua NULL berbeda. Tidakkah seharusnya ini menyiratkan bahwa semua NULL adalah unik dan indeks unik harus memungkinkan sejumlah NULL?

pengguna87166
sumber
Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Paul White mengatakan GoFundMonica

Jawaban:

52

Mengapa bisa seperti ini? Karena pada saat itu, seseorang membuat keputusan desain tanpa mengetahui atau peduli tentang apa yang dikatakan standar (setelah semua, kita memang memiliki semua jenis perilaku aneh dengan NULL, dan dapat memaksa perilaku yang berbeda sesuka hati). Keputusan itu menentukan bahwa, dalam hal ini NULL = NULL,.

Itu bukan keputusan yang sangat cerdas. Apa yang seharusnya mereka lakukan adalah memiliki perilaku default yang mematuhi standar ANSI, dan jika mereka benar-benar menginginkan perilaku aneh ini, ijinkan itu melalui opsi DDL seperti WITH CONSIDER_NULLS_EQUALatau WITH ALLOW_ONLY_ONE_NULL.

Tentu saja, 20-20 adalah belakang.

Dan kami memiliki solusi, sekarang, bagaimanapun, bahkan jika itu bukan yang terbersih atau paling intuitif.

Anda bisa mendapatkan perilaku ANSI yang tepat di SQL Server 2008 dan di atasnya dengan membuat indeks yang unik dan difilter.

CREATE UNIQUE INDEX foo ON dbo.bar(key) WHERE key IS NOT NULL;

Ini memungkinkan lebih dari satu NULLnilai karena baris-baris tersebut sama sekali tidak disertakan dalam pemeriksaan duplikat. Sebagai bonus tambahan, ini akan menjadi indeks yang lebih kecil dari satu yang terdiri dari seluruh tabel jika beberapa NULLs diizinkan (terutama ketika itu bukan satu-satunya kolom dalam indeks, memiliki INCLUDEkolom, dll). Namun, Anda mungkin ingin mengetahui beberapa batasan lain dari indeks yang difilter:

Aaron Bertrand
sumber
8

Benar. Penerapan kendala atau indeks unik di server sql memungkinkan satu dan hanya satu NULL. Benar juga bahwa ini secara teknis tidak sesuai dengan definisi NULL tetapi itu adalah salah satu hal yang mereka lakukan untuk membuatnya lebih berguna walaupun itu "secara teknis" tidak benar. Perhatikan KUNCI UTAMA (juga indeks unik) tidak memungkinkan NULL (tentu saja).

Kenneth Fisher
sumber
1
Teknis (SQL-Server) ini juga tidak sesuai dengan standar SQL. Ada item Connect berusia 7 tahun tentang masalah ini.
ypercubeᵀᴹ
@ ypercube Benar. Itu sebabnya saya katakan itu hanya implementasi dan tidak benar-benar cocok dengan definisi NULL. Saya belum memikirkan indeks unik yang difilter (walaupun saya sudah menggunakannya untuk hal-hal lain.)
Kenneth Fisher
3

Pertama - berhenti menggunakan frasa "Nilai kosong", itu hanya akan membuat Anda tersesat. Alih-alih, gunakan frasa "penanda nol" - penanda di kolom yang menunjukkan bahwa nilai aktual di kolom ini hilang atau tidak dapat diterapkan (tetapi perhatikan bahwa penanda tidak mengatakan opsi mana yang sebenarnya merupakan kasus¹).

Sekarang, bayangkan berikut ini (di mana basis data tidak memiliki pengetahuan lengkap tentang situasi yang dimodelkan).

Situation          Database

ID   Code          ID   Code
--   -----         --   -----
1    A             1    A
2    B             2    (null)
3    C             3    C
4    B             4    (null)

Aturan integritas yang kami modelkan adalah "Kode harus unik". Situasi dunia nyata melanggar ini, jadi database seharusnya tidak membiarkan kedua item 2 dan 4 berada di tabel pada saat yang sama.

Pendekatan teraman, dan paling tidak fleksibel, adalah dengan melarang penanda nol di bidang Kode, sehingga tidak ada kemungkinan data tidak konsisten. Pendekatan yang paling fleksibel adalah dengan memungkinkan beberapa penanda nol dan khawatir tentang keunikan ketika nilai dimasukkan.

Programmer Sybase menggunakan pendekatan yang agak aman, tidak terlalu fleksibel dengan hanya mengizinkan satu penanda nol di tabel - sesuatu yang dikeluhkan oleh komentator sejak saat itu. Microsoft telah melanjutkan perilaku ini, saya kira untuk kompatibilitas ke belakang.


¹ Saya yakin saya membaca suatu tempat yang Codd pertimbangkan untuk mengimplementasikan dua penanda nol - satu untuk tidak diketahui, satu untuk tidak dapat diterapkan - tetapi menolaknya, tetapi saya tidak dapat menemukan referensi. Apakah saya mengingat dengan benar?

PS Kutipan favorit saya tentang null: Louis Davidson, "Desain Database SQL Server 2000 Profesional", Wrox Press, 2001, halaman 52. "Dididihkan menjadi satu kalimat: NULL itu jahat."

Greenstone Walker
sumber
1
Mengizinkan satu nullpun tidak mencapai tujuan ini. Karena nilai yang hilang mungkin ternyata sama dengan nilai di salah satu baris lainnya.
Martin Smith
1
Apa yang dikatakan @MartinSmith. Bagaimana jika Anda memiliki batasan pemeriksaan CHECK (Value IN ('A','B','C','D'))? Kemudian implementasi SQL-Server dan standar SQL memungkinkan tabel untuk memiliki 5 baris (satu baris untuk setiap nilai ditambah 1 dengan NULL.) Kemudian, sementara database konsisten dengan kendala, itu tidak konsisten dengan maksud perancang untuk tabel memiliki maksimal 4 baris. Tidak ada nilai yang NULL dapat diubah menjadi yang tidak akan melanggar kendala, kecuali satu atau lebih baris dihapus.
ypercubeᵀᴹ
1
Fakta bahwa standar akan memungkinkan 6 bahkan 106 baris, bukannya 5 tidak mengubah bahwa keduanya gagal dalam beberapa cara dalam skenario ini.
ypercubeᵀᴹ
@ Martin Smith, mungkin, tapi sekali lagi, mungkin tidak - server basis data tidak bisa memastikannya sehingga tidak mengambil risiko dan mengambil rute yang aman. Itulah yang diputuskan oleh pemrogram Sybase (saya kira), menyebabkan gangguan sejak (setidaknya sejauh di dalam SQL Server 6.5, buku tertua di rak buku saya, tempat Ron Soukup membuat komentar yang hampir sama dengan yang dilakukan Aaron Bertrand dalam jawabannya) . Saya kira itu bisa lebih buruk - mereka bisa diamanatkan tidak ada penanda nol. :-)
Greenstone Walker
2
@GreenstoneWalker - Tidak mengambil rute "aman". Ini mengasumsikan bahwa nilai yang hilang tidak akan konflik. CREATE TABLE #T(A INT NULL UNIQUE);INSERT INTO #T VALUES (1),(NULL);UPDATE #T SET A = 1 WHERE A IS NULL;akan memunculkan kesalahan. Menurut teori motivasi desain Anda, Anda harus mencegah penyisipan NULLdalam kasus pertama - karena pengetahuan yang tidak lengkap berarti tidak ada jaminan bahwa nilainya berbeda.
Martin Smith
2

Ini mungkin tidak akurat secara teknis, tetapi secara filosofis itu membantu saya tidur di malam hari ...

Seperti beberapa yang dikatakan atau disinggung, jika Anda menganggap NULL sebagai tidak dikenal, maka Anda tidak dapat menentukan apakah satu nilai NULL sebenarnya sama dengan nilai NULL lainnya. Berpikir seperti ini, ekspresi NULL == NULL harus dievaluasi menjadi NULL, artinya tidak diketahui.

Batasan unik akan membutuhkan nilai definitif untuk perbandingan nilai kolom. Dengan kata lain, ketika membandingkan nilai kolom tunggal terhadap nilai kolom lainnya menggunakan operator kesetaraan, itu harus bernilai false agar valid. Tidak diketahui tidak benar-benar salah meskipun sering dianggap sebagai kesalahan. Dua nilai NULL bisa sama, atau tidak ... itu tidak bisa ditentukan secara definitif.

Ini membantu untuk memikirkan kendala unik sebagai membatasi nilai-nilai yang dapat ditentukan berbeda satu sama lain. Yang saya maksud dengan ini adalah jika Anda menjalankan SELECT yang terlihat seperti ini:

SELECT * from dbo.table1 WHERE ColumnWithUniqueContraint="some value"

Kebanyakan orang akan mengharapkan satu hasil, mengingat bahwa ada kendala unik. Jika Anda mengizinkan beberapa nilai NULL di ColumnWithUniqueConstraint, maka tidak mungkin untuk memilih satu baris berbeda dari tabel menggunakan NULL sebagai nilai yang dibandingkan.

Mengingat itu, saya percaya bahwa terlepas dari apakah itu diterapkan secara akurat atau tidak sehubungan dengan definisi NULL, itu pasti jauh lebih praktis dalam kebanyakan situasi daripada membiarkan beberapa nilai NULL.

EricJ
sumber
Pilih Anda akan memberikan 1 hasil, ketika ada kendala unik (dalam implementasi apa pun, tidak hanya SQL-Server). Apa maksudmu
ypercubeᵀᴹ
-3

Salah satu tujuan utama dari UNIQUEkendala adalah untuk mencegah rekaman duplikat. Jika seseorang perlu memiliki tabel di mana ada beberapa catatan di mana nilai "tidak diketahui", tetapi tidak ada dua catatan yang diizinkan memiliki nilai "diketahui" yang sama, maka nilai yang tidak diketahui harus ditugaskan pengidentifikasi unik buatan sebelum mereka ditambahkan ke tabel.

Ada beberapa kasus langka di mana kolom yang memiliki UNIQUEbatasan dan berisi nilai nol tunggal; misalnya, jika tabel berisi pemetaan antara nilai kolom dan deskripsi teks lokal, baris untuk NULLakan memungkinkan untuk menentukan deskripsi yang akan muncul ketika kolom itu di beberapa tabel lainnya NULL. Perilaku NULLmemungkinkan untuk kasus penggunaan itu.

Kalau tidak, saya tidak melihat dasar untuk database dengan UNIQUEbatasan pada kolom apa pun untuk memungkinkan adanya banyak catatan yang identik, tetapi saya tidak melihat cara untuk mencegah itu sementara memungkinkan beberapa catatan yang nilai kuncinya tidak dapat dibedakan. Mendeklarasikan yang NULLtidak sama dengan dirinya sendiri tidak akan membuat NULLnilai dibedakan satu sama lain.

supercat
sumber
3
Pengidentifikasi unik buatan adalah lelucon, maaf. Bagaimana Anda akan melakukannya untuk VIN? Jika Anda tidak tahu apa itu, mengapa membuat sesuatu? Hanya untuk mengambil ruang disk tambahan? Sepertinya omong kosong untuk mengatasi beberapa masalah lain (seperti tidak ingin menulis aplikasi sedemikian rupa sehingga dengan anggun menangani NULLs). Jika Anda benar-benar perlu tahu mengapa sesuatu NULL (ada tetapi tidak diketahui vs tahu itu tidak ada vs tidak tahu atau peduli jika ada, misalnya), lalu tambahkan beberapa jenis kolom status. Token hanya menyebabkan kode trickle down yang aneh untuk menghadapinya.
Aaron Bertrand
Banyak tergantung pada tujuan dari kendala keunikan. Jika bidang akan digunakan sebagai pengidentifikasi, itu tidak boleh nol. Dalam kasus (seperti halnya VIN) di mana aturan bisnis menyarankan bahwa ketika sebuah item muncul dua kali, salah satunya pasti salah, tetapi beberapa item mungkin "tidak tahu", kendala keunikan tidak terasa seperti pendekatan yang tepat. Jika seseorang memiliki kendaraan dengan VIN yang dikenal, dan itu bertentangan dengan yang lain dalam database, orang mungkin tahu bahwa setidaknya salah satu VIN salah, tetapi akan lebih baik untuk memiliki database melaporkan nilai yang dipercaya untuk kedua catatan daripada menebak yang itu benar.
supercat
@AaronBertrand: Ada beberapa kasus di mana bidang unik-jika-tidak-nol yang mungkin-null perlu menjadi kunci pengganti tidak dapat dibuat sebelum mengisi bidang tersebut (misalnya "ID pasangan"), tetapi dalam situasi seperti bahwa batasan "unik" tidak akan memadai; akan diperlukan bahwa jika X.Spouse adalah non-null, X.Spouse.Spouse = X. Kebetulan, sesuatu seperti "pasangan" juga dapat ditangani dengan mengatakan bahwa catatan untuk orang yang belum menikah seharusnya tidak memiliki "NULL" sebagai pasangan, melainkan ID sendiri, dalam hal ini X.spouse.spouse = X aturan bisa berlaku untuk semua orang.
supercat