Artikel ini adalah Bagian 4 dalam seri tentang kompleksitas NULL. Dalam artikel sebelumnya (Bagian 1, Bagian 2, dan Bagian 3), saya membahas arti NULL sebagai penanda untuk nilai yang hilang, bagaimana perilaku NULL dalam perbandingan dan elemen kueri lainnya, dan fitur penanganan NULL standar yang tidak belum tersedia di T-SQL. Bulan ini saya membahas perbedaan antara cara batasan unik didefinisikan dalam standar ISO/IEC SQL dan cara kerjanya di T-SQL. Saya juga akan memberikan solusi khusus yang dapat Anda terapkan jika Anda memerlukan fungsionalitas standar.
Batasan UNIK standar
SQL Server menangani NULL seperti nilai non-NULL untuk tujuan menegakkan batasan unik. Artinya, batasan unik pada T dipenuhi jika dan hanya jika tidak ada dua baris R1 dan R2 dari T sedemikian sehingga R1 dan R2 memiliki kombinasi nilai NULL dan non-NULL yang sama dalam kolom unik. Misalnya, Anda mendefinisikan batasan unik pada col1, yang merupakan kolom NULLable dari tipe data INT. Upaya untuk memodifikasi tabel dengan cara yang akan menghasilkan lebih dari satu baris dengan NULL di col1 akan ditolak, sama seperti modifikasi yang akan menghasilkan lebih dari satu baris dengan nilai 1 di col1 akan ditolak.
Misalkan Anda mendefinisikan batasan unik komposit pada kombinasi kolom NULLable INT col1 dan col2. Upaya untuk memodifikasi tabel dengan cara yang akan menghasilkan lebih dari satu kemunculan salah satu kombinasi nilai (col1, col2) berikut ini akan ditolak:(NULL, NULL), (3, NULL), (NULL, 300 ), (1, 100).
Jadi seperti yang Anda lihat, implementasi T-SQL dari batasan unik memperlakukan NULL seperti nilai non-NULL untuk tujuan penegakan keunikan.
Jika Anda ingin mendefinisikan kunci asing pada beberapa tabel X yang mereferensikan beberapa tabel Y, Anda harus menerapkan keunikan pada kolom yang direferensikan dengan salah satu opsi berikut:
- Kunci utama
- Batasan unik
- Indeks unik tanpa filter
Kunci utama tidak diperbolehkan pada kolom NULLable. Baik batasan unik (yang membuat indeks di bawah sampul) dan indeks unik yang dibuat secara eksplisit diizinkan pada kolom NULLable, dan menegakkan keunikannya dalam T-SQL menggunakan logika yang disebutkan di atas. Tabel referensi diperbolehkan memiliki baris dengan NULL di kolom referensi, terlepas dari apakah tabel referensi memiliki baris dengan NULL di kolom referensi. Idenya adalah untuk mendukung hubungan opsional. Beberapa baris dalam tabel referensi dapat berupa baris yang tidak terkait dengan baris mana pun dalam tabel referensi. Anda akan menerapkan ini dengan menggunakan NULL di kolom referensi.
Untuk mendemonstrasikan implementasi T-SQL dari batasan unik, jalankan kode berikut, yang membuat tabel bernama T3 dengan batasan unik yang ditentukan pada kolom NULLable INT col1, dan mengisinya dengan beberapa baris sampel:
GUNAKAN tempdb;GO DROP TABLE JIKA ADA dbo.T3;GO CREATE TABLE dbo.T3(col1 INT NULL, col2 INT NULL, CONSTRAINT UNQ_T3 UNIQUE(col1)); INSERT INTO dbo.T3(col1, col2) NILAI(1, 100),(2, -1),(NULL, -1),(3, 300);
Gunakan kode berikut untuk membuat tabel kueri:
PILIH * DARI dbo.T3;
Kueri ini menghasilkan keluaran berikut:
col1 col2----------- -----------1 1002 -1NULL -13 300
Mencoba menyisipkan baris kedua dengan NULL di col1:
MASUKKAN KE dbo.T3(col1, col2) VALUES(NULL, 400);
Upaya ini ditolak dan Anda mendapatkan kesalahan berikut:
Msg 2627, Level 14, Status 1Pelanggaran batasan KUNCI UNIK 'UNQ_T3'. Tidak dapat menyisipkan kunci duplikat di objek 'dbo.T3'. Nilai kunci duplikat adalah (
Definisi batasan unik standar sedikit berbeda dari versi T-SQL. Perbedaan utama berkaitan dengan penanganan NULL. Berikut definisi batasan unik dari standar:
“Sebuah batasan unik pada T terpenuhi jika dan hanya jika tidak ada dua baris R1 dan R2 dari T sedemikian rupa sehingga R1 dan R2 memiliki nilai non-NULL yang sama di kolom unik.”
Jadi, tabel T dengan batasan unik pada col1 akan mengizinkan beberapa baris dengan NULL di col1, tetapi melarang beberapa baris dengan nilai non-NULL yang sama di col1.
Yang agak sulit dijelaskan adalah apa yang terjadi sesuai standar dengan batasan unik komposit. Katakanlah Anda memiliki batasan unik yang ditentukan pada (col1, col2). Anda dapat memiliki beberapa baris dengan (NULL, NULL), tetapi Anda tidak dapat memiliki banyak baris dengan (3, NULL), sama seperti Anda tidak dapat memiliki banyak baris dengan (1, 100). Demikian pula, Anda tidak dapat memiliki banyak baris dengan (NULL, 300). Intinya adalah Anda tidak diperbolehkan memiliki beberapa baris dengan nilai non-NULL yang sama di kolom unik. Adapun kunci asing, Anda dapat memiliki sejumlah baris di tabel referensi dengan NULL di semua kolom referensi, terlepas dari apa yang ada di tabel referensi. Baris tersebut tidak terkait dengan baris mana pun dalam tabel yang direferensikan (hubungan opsional). Namun, jika Anda memiliki nilai non-NULL di salah satu kolom referensi, harus ada baris di tabel referensi dengan nilai non-NULL yang sama di kolom referensi.
Misalkan Anda memiliki database dalam platform yang mendukung batasan unik standar dan Anda perlu memigrasikan database tersebut ke SQL Server. Anda mungkin menghadapi masalah dengan penerapan batasan unik di SQL Server jika kolom unik mendukung NULL. Data yang dianggap valid di sistem sumber mungkin dianggap tidak valid di SQL Server. Di bagian berikut, saya akan menjelajahi sejumlah kemungkinan solusi di SQL Server.
Solusi 1, menggunakan indeks terfilter atau tampilan terindeks
Solusi umum di T-SQL untuk menerapkan fungsionalitas batasan unik standar ketika hanya ada satu kolom target yang terlibat adalah dengan menggunakan indeks terfilter unik yang memfilter hanya baris di mana kolom target bukan NULL. Kode berikut menghapus batasan unik yang ada dari T3 dan mengimplementasikan indeks seperti itu:
ALTER TABLE dbo.T3 DROP CONSTRAINT UNQ_T3; BUAT INDEKS NONCLUSTERED UNIK idx_col1_notnull PADA dbo.T3(col1) DI MANA col1 TIDAK NULL;
Karena indeks hanya memfilter baris di mana col1 bukan NULL, properti UNIQUE hanya diterapkan pada nilai col1 non-NULL.
Ingat bahwa T3 sudah memiliki baris dengan NULL di col1. Untuk menguji solusi ini, gunakan kode berikut untuk menambahkan baris kedua dengan NULL di col1:
MASUKKAN KE dbo.T3(col1, col2) VALUES(NULL, 400);
Kode ini berhasil dijalankan.
Ingat bahwa T3 sudah memiliki baris dengan nilai 1 di col1. Jalankan kode berikut untuk mencoba menambahkan baris kedua dengan 1 di col1:
MASUKKAN KE dbo.T3(col1, col2) VALUES(1, 500);
Seperti yang diharapkan, upaya ini gagal dengan kesalahan berikut:
Msg 2601, Level 14, Status 1Tidak dapat menyisipkan baris kunci duplikat di objek 'dbo.T3' dengan indeks unik 'idx_col1_notnull'. Nilai kunci duplikat adalah (1).
Gunakan kode berikut untuk menanyakan T3:
PILIH * DARI dbo.T3;
Kode ini menghasilkan output berikut yang menunjukkan dua baris dengan NULL di col1:
col1 col2----------- -----------1 1002 -1NULL -13 300NULL 400
Solusi ini berfungsi dengan baik saat Anda perlu menerapkan keunikan hanya pada satu kolom, dan saat Anda tidak perlu menerapkan integritas referensial dengan kunci asing yang menunjuk ke kolom tersebut.
Masalah dengan kunci asing adalah bahwa SQL Server memerlukan kunci utama atau batasan unik atau indeks nonfilter unik yang ditentukan pada kolom yang direferensikan. Ini tidak berfungsi ketika hanya ada indeks terfilter unik yang ditentukan pada kolom yang direferensikan. Mari kita coba membuat tabel dengan referensi kunci asing T3.col1. Pertama, gunakan kode berikut untuk membuat tabel T3:
DROP TABLE JIKA ADA dbo.T3FK;GO CREATE TABLE dbo.T3FK( id INT NOT NULL IDENTITY CONSTRAINT PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL);
Kemudian coba jalankan kode berikut untuk menambahkan kunci asing yang menunjuk dari T3FK.col1 ke T3.col1:
ALTER TABLE dbo.T3FK ADD CONSTRAINT FK_T3_T3FK FOREIGN KEY(col1) REFERENSI dbo.T3(col1);
Upaya ini gagal dengan kesalahan berikut:
Msg 1776, Level 16, State 0Tidak ada kunci utama atau kunci kandidat dalam tabel referensi 'dbo.T3' yang cocok dengan daftar kolom referensi di kunci asing 'FK_T3_T3FK'.
Msg 1750, Level 16, Status 1
Tidak dapat membuat batasan atau indeks. Lihat kesalahan sebelumnya.
Pada titik ini, hapus indeks terfilter yang ada untuk pembersihan:
JADIKAN INDEKS idx_col1_notnull PADA dbo.T3;
Jangan jatuhkan tabel T3FK, karena Anda akan menggunakannya dalam contoh selanjutnya.
Masalah lain dengan solusi indeks yang difilter, dengan asumsi Anda tidak memerlukan kunci asing, adalah bahwa itu tidak berfungsi ketika Anda perlu menerapkan fungsionalitas batasan unik standar pada beberapa kolom, misalnya pada kombinasi (col1, col2) . Ingat bahwa batasan unik standar tidak mengizinkan kombinasi nilai non-NULL duplikat di kolom unik. Untuk menerapkan logika ini dengan indeks yang difilter, Anda hanya perlu memfilter baris yang kolom uniknya tidak NULL. Dengan frasa yang berbeda, Anda hanya perlu memfilter baris yang tidak memiliki NULL di semua kolom unik. Sayangnya, indeks yang difilter hanya mengizinkan ekspresi yang sangat sederhana. Mereka tidak mendukung OR, NOT atau manipulasi pada kolom. Jadi tidak ada definisi indeks berikut yang saat ini didukung:
BUAT INDEKS NONCLUSTERED UNIK idx_customunique PADA dbo.T3(col1, col2) DI MANA col1 TIDAK NULL ATAU col2 TIDAK NULL; BUAT INDEKS NONCLUSTERED UNIK idx_customunique PADA dbo.T3(col1, col2) DI MANA TIDAK (col1 IS NULL DAN col2 IS NULL); BUAT INDEKS NONCLUSTERED UNIK idx_customunique PADA dbo.T3(col1, col2) DI MANA COALESCE(col1, col2) BUKAN NULL;
Solusi dalam kasus seperti itu adalah membuat tampilan yang diindeks berdasarkan kueri yang mengembalikan col1 dan col2 dari T3 dengan salah satu klausa WHERE di atas, dengan indeks berkerumun unik pada (col1, col2), seperti:
BUAT TAMPILAN dbo.T3CustomUnique DENGAN SKEMABINDINGAS PILIH col1, col2 DARI dbo.T3 DI MANA col1 TIDAK NULL ATAU col2 TIDAK NULL;GO CREATE UNIQUE CLUSTERED INDEX idx_col1_col2 PADA dbo.T3CustomUnique);GO Anda akan diizinkan untuk menambahkan beberapa baris dengan (NULL, NULL) di (col1, col2), tetapi Anda tidak akan diizinkan untuk menambahkan beberapa kemunculan kombinasi nilai non-NULL di (col1, col2), seperti (3 , NULL) atau (NULL, 300) atau (1, 100). Namun, solusi ini tidak mendukung kunci asing.Pada titik ini, jalankan kode berikut untuk pembersihan:
LIHAT JIKA ADA dbo.T3CustomUnique;Solusi 2, menggunakan kunci pengganti dan kolom yang dihitung
Solusi dengan indeks yang difilter dan tampilan yang diindeks bagus selama Anda tidak perlu mendukung kunci asing. Tetapi bagaimana jika Anda perlu menegakkan integritas referensial? Salah satu opsi adalah tetap menggunakan indeks terfilter atau solusi tampilan terindeks untuk menegakkan keunikan, dan menggunakan pemicu untuk menegakkan integritas referensial. Namun, opsi ini cukup mahal.
Pilihan lain adalah menggunakan solusi yang sama sekali berbeda untuk bagian keunikan yang mendukung kunci asing. Solusinya melibatkan penambahan dua kolom ke tabel yang direferensikan (T3 dalam kasus kami). Satu kolom yang disebut id adalah kunci pengganti dengan properti identitas. Kolom lain yang disebut flag adalah kolom terkomputasi tetap yang mengembalikan id ketika col1 adalah NULL dan 0 ketika bukan NULL. Anda kemudian menerapkan batasan unik pada kombinasi col1 dan flag. Berikut kode untuk menambahkan dua kolom dan batasan unik:
ALTER TABLE dbo.T3 ADD id INT NOT NULL IDENTITY, tandai SEBAGAI KASUS KETIKA col1 NULL MAKA id ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3_col1_flag UNIQUE(col1, flag);Gunakan kode berikut untuk menanyakan T3:
PILIH * DARI dbo.T3;Kode ini menghasilkan output berikut:
col1 col2 id flag----------- ----------- ----------- ---------- -1 100 1 02 -1 2 0NULL -1 3 33 300 4 0NULL 400 5 5Adapun tabel referensi (T3FK dalam kasus kami), Anda menambahkan kolom terhitung yang disebut flag yang selalu disetel ke 0, dan kunci asing yang ditentukan pada (col1, flag) yang menunjuk ke kolom unik T3 (col1, flag), seperti itu :
ALTER TABLE dbo.T3FK ADD flag AS 0 PERSISTED, CONSTRAINT FK_T3_T3FK FOREIGN KEY(col1, flag) REFERENCES dbo.T3(col1, flag);Mari kita uji solusi ini.
Coba tambahkan baris berikut:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES (1, 100, 'A'), (2, -1, 'B'), (3, 300, 'C');Baris ini berhasil ditambahkan, sebagaimana mestinya, karena semua memiliki baris referensi yang sesuai.
Buat kueri tabel T3FK:
PILIH * DARI dbo.T3FK;Anda mendapatkan output berikut:
id col1 col2 flag col2 lainnya----------- ----------- ----------- --------- - -----------1 1 100 A 02 2 -1 B 03 3 300 C 0Coba tambahkan baris yang tidak memiliki baris yang sesuai di tabel referensi:
MASUKKAN KE dbo.T3FK(col1, col2, othercol) VALUES (4, 400, 'D');Upaya ditolak, sebagaimana mestinya, dengan kesalahan berikut:
Msg 547, Level 16, State 0
Pernyataan INSERT bertentangan dengan batasan FOREIGN KEY "FK_T3_T3FK". Konflik terjadi di database "TSQLV5", tabel "dbo.T3".Coba tambahkan baris ke T3FK dengan NULL di col1:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES (NULL, NULL, 'E');Baris ini dianggap tidak terkait dengan baris mana pun di T3FK (hubungan opsional) dan, menurut standar, harus diizinkan terlepas dari apakah NULL ada di tabel referensi di col1. T-SQL mendukung skenario ini, dan baris berhasil ditambahkan.
Buat kueri tabel T3FK:
PILIH * DARI dbo.T3FK;Kode ini menghasilkan output berikut:
id col1 col2 flag col2 lainnya----------- ----------- ----------- --------- - -----------1 1 100 A 02 2 -1 B 03 3 300 C 05 NULL NULL E 0Solusinya bekerja dengan baik ketika Anda perlu menerapkan fungsionalitas keunikan standar pada satu kolom. Tetapi memiliki masalah ketika Anda perlu menerapkan keunikan pada banyak kolom. Untuk mendemonstrasikan masalah, pertama-tama jatuhkan tabel T3 dan T3FK:
DROP TABLE JIKA ADA dbo.T3FK, dbo.T3;Gunakan kode berikut untuk membuat ulang T3 dengan batasan unik gabungan pada (col1, col2, flag):
BUAT TABEL dbo.T3( col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, tandai SEBAGAI KASUS KETIKA col1 NULL DAN col2 NULL MAKA id ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3 UNIK(col1, col2, flag ));Perhatikan bahwa flag diatur ke id ketika col1 dan col2 keduanya NULL dan 0 sebaliknya.
Batasan unik itu sendiri berfungsi dengan baik.
Jalankan kode berikut untuk menambahkan beberapa baris ke T3, termasuk beberapa kemunculan (NULL, NULL) di (col1, col2):
MASUKKAN KE dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);Baris ini berhasil ditambahkan sebagaimana mestinya.
Coba tambahkan dua kemunculan (1, NULL) di (col1, col2):
MASUKKAN KE dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);Upaya ini gagal sebagaimana mestinya dengan kesalahan berikut:
Msg 2627, Level 14, Status 1
Pelanggaran batasan KUNCI UNIK 'UNQ_T3'. Tidak dapat menyisipkan kunci duplikat di objek 'dbo.T3'. Nilai kunci duplikat adalah (1,, 0). Coba tambahkan dua kemunculan (NULL, 100) di (col1, col2):
MASUKKAN KE dbo.T3(col1, col2) VALUES(NULL, 100),(NULL, 100);Upaya ini juga gagal sebagaimana mestinya dengan kesalahan berikut:
Msg 2627, Level 14, Status 1
Pelanggaran batasan KUNCI UNIK 'UNQ_T3'. Tidak dapat menyisipkan kunci duplikat di objek 'dbo.T3'. Nilai kunci duplikat adalah (, 100, 0). Coba tambahkan dua baris berikut agar tidak terjadi pelanggaran:
MASUKKAN KE dbo.T3(col1, col2) VALUES(3, NULL),(NULL, 300);Baris ini berhasil ditambahkan.
Buat kueri tabel T3 saat ini:
PILIH * DARI dbo.T3;Anda mendapatkan output berikut:
col1 col2 id flag----------- ----------- ----------- ---------- -1 100 1 01 200 2 0NULL NULL 3 3NULL NULL 4 43 NULL 9 0NULL 300 10 0Sejauh ini bagus.
Selanjutnya, jalankan kode berikut untuk membuat tabel T3FK dengan kunci asing komposit yang mereferensikan kolom unik T3:
CREATE TABLE dbo.T3FK( id INT NOT NULL IDENTITY CONSTRAINT PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL, flag AS 0 PERSISTED, CONSTRAINT FK_T3_T3FK ) REFERENSI dbo.T3(col1, col2, flag));Solusi ini secara alami memungkinkan penambahan baris ke T3FK dengan (NULL, NULL) di (col1, col2). Masalahnya adalah itu juga memungkinkan menambahkan baris NULL baik di col1 atau col2, bahkan ketika kolom lainnya bukan NULL, dan tabel yang direferensikan T3 tidak memiliki kombinasi tombol seperti itu. Misalnya, coba tambahkan baris berikut ke T3FK:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');Baris ini berhasil ditambahkan meskipun tidak ada baris terkait di T3. Menurut standar, baris ini seharusnya tidak diperbolehkan.
Kembali ke papan gambar…
Solusi 3, menggunakan kunci pengganti dan kolom yang dihitung
Masalah dengan solusi sebelumnya (Solusi 2) muncul ketika Anda perlu mendukung kunci asing komposit. Ini memungkinkan baris dalam tabel referensi yang memiliki NULL di daftar satu kolom referensi, bahkan ketika ada nilai non-NULL di kolom referensi lain, dan tidak ada baris terkait di tabel referensi. Untuk mengatasinya, Anda dapat menggunakan variasi dari solusi sebelumnya, yang kami sebut Solusi 3.
Pertama, gunakan kode berikut untuk menghapus tabel yang ada:
DROP TABLE JIKA ADA dbo.T3FK, dbo.T3;Dalam solusi baru di tabel yang direferensikan (T3 dalam kasus kami), Anda masih menggunakan kolom kunci pengganti id berbasis identitas. Anda juga menggunakan kolom terhitung yang disebut unqpath. Ketika semua kolom unik (col1 dan col2 dalam contoh kita) adalah NULL, Anda menyetel unqpath ke representasi string karakter id (tidak ada pemisah ). Ketika salah satu kolom unik bukan NULL, Anda menyetel unqpath ke representasi string karakter dari daftar terpisah dari nilai kolom unik menggunakan fungsi CONCAT. Fungsi ini menggantikan NULL dengan string kosong. Yang penting adalah memastikan untuk menggunakan pemisah yang biasanya tidak muncul di data itu sendiri. Misalnya, dengan nilai integer col1 dan col2 Anda hanya memiliki digit, jadi pemisah apa pun selain digit akan berfungsi. Dalam contoh saya, saya akan menggunakan titik (.). Anda kemudian menerapkan batasan unik pada unqpath. Anda tidak akan pernah memiliki konflik antara nilai unqpath ketika semua kolom unik adalah NULL (diatur ke id) versus ketika salah satu kolom unik bukan NULL karena dalam kasus sebelumnya unqpath tidak mengandung pemisah, dan dalam kasus terakhir itu tidak . Ingatlah bahwa Anda akan menggunakan Solusi 3 ketika Anda memiliki kotak kunci komposit, dan kemungkinan lebih memilih Solusi 2, yang lebih sederhana, ketika Anda memiliki kotak kunci kolom tunggal. Jika Anda ingin menggunakan Solusi 3 juga dengan kunci satu kolom dan bukan Solusi 2, pastikan Anda menambahkan pemisah ketika kolom unik bukan NULL meskipun hanya ada satu nilai yang terlibat. Dengan cara ini Anda tidak akan memiliki konflik ketika id di baris di mana col1 adalah NULL sama dengan col1 di baris lain, karena yang pertama tidak akan memiliki pemisah dan yang terakhir akan.
Berikut kode untuk membuat T3 dengan tambahan yang disebutkan di atas:
BUAT TABEL dbo.T3( col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, unqpath AS CASE KETIKA col1 NULL DAN col2 NULL MAKA CAST(id AS VARCHAR(10)) ELSE CONCAT(CAST(col1 AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE(unqpath));Sebelum berurusan dengan kunci asing dan tabel referensi, mari kita uji batasan unik. Ingat, seharusnya tidak mengizinkan kombinasi duplikat nilai non-NULL di kolom unik, tetapi seharusnya mengizinkan beberapa kemunculan semua-NULL di kolom unik.
Jalankan kode berikut untuk menambahkan beberapa baris, termasuk dua kemunculan (NULL, NULL) di (col1, col2):
MASUKKAN KE dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);Kode ini berhasil diselesaikan sebagaimana mestinya.
Coba tambahkan dua kemunculan (1, NULL) di (col1, col2):
MASUKKAN KE dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);Kode ini gagal dengan kesalahan berikut sebagaimana mestinya:
Msg 2627, Level 14, Status 1
Pelanggaran batasan KUNCI UNIK 'UNQ_T3'. Tidak dapat menyisipkan kunci duplikat di objek 'dbo.T3'. Nilai kunci duplikat adalah (1.).Demikian pula, upaya berikut juga ditolak:
MASUKKAN KE dbo.T3(col1, col2) VALUES(NULL, 100),(NULL, 100);Anda mendapatkan kesalahan berikut:
Msg 2627, Level 14, Status 1
Pelanggaran batasan KUNCI UNIK 'UNQ_T3'. Tidak dapat menyisipkan kunci duplikat di objek 'dbo.T3'. Nilai kunci duplikat adalah (.100).Jalankan kode berikut untuk menambahkan beberapa baris lagi:
MASUKKAN KE dbo.T3(col1, col2) VALUES(3, NULL),(NULL, 300);Kode ini berhasil berjalan sebagaimana mestinya.
Pada titik ini, kueri T3:
PILIH * DARI dbo.T3;Anda mendapatkan output berikut:
col1 col2 id unqpath----------- ----------- ----------- ---------- -------------1 100 1 1.1001 200 2 1.200NULL NULL 3 3NULL NULL 4 43 NULL 9 3.NULL 300 10 .300Amati nilai unqpath dan pastikan Anda memahami logika di balik konstruksinya, dan perbedaan antara kasus di mana semua kolom unik adalah NULL (tanpa pemisah) versus ketika setidaknya satu bukan NULL (ada pemisah).
Adapun tabel referensi, T3FK; Anda juga mendefinisikan kolom yang dihitung yang disebut unqpath, tetapi dalam kasus di mana semua kolom referensi adalah NULL, Anda mengatur kolom ke NULL—bukan id. Ketika salah satu kolom referensi bukan NULL, Anda membuat daftar nilai terpisah yang sama seperti yang Anda lakukan di T3. Anda kemudian menentukan kunci asing di T3FK.unqpath menunjuk ke T3.unqpath, seperti:
CREATE TABLE dbo.T3FK( id INT NOT NULL IDENTITY CONSTRAINT PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL, unqpath AS CASE WHEN col1 IS NULL AND col2 IS NULL (CAST(col1 AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) END PERSISTED, CONSTRAINT FK_T3_T3FK FOREIGN KEY(unqpath) REFERENCES dbo.T3(unqpath));Kunci asing ini akan menolak baris di T3FK di mana salah satu kolom referensi tidak NULL, dan tidak ada baris terkait di tabel referensi T3, seperti yang ditunjukkan oleh upaya berikut:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');Kode ini menghasilkan kesalahan berikut:
Msg 547, Level 16, State 0
Pernyataan INSERT bertentangan dengan batasan FOREIGN KEY "FK_T3_T3FK". Konflik terjadi pada database "TSQLV5", tabel "dbo.T3", kolom 'unqpath'.Solusi ini akan baris di T3FK di mana salah satu kolom referensi tidak NULL selama baris terkait di T3 ada, serta baris dengan NULL di semua kolom referensi, karena baris tersebut dianggap tidak terkait dengan setiap baris di T3. Kode berikut menambahkan baris valid tersebut ke T3FK:
MASUKKAN KE dbo.T3FK(col1, col2, othercol) NILAI (1 , 100 , 'A'), (1 , 200 , 'B'), (3 , NULL, 'C'), (NULL, 300 , 'D'), (NULL, NULL, 'E'), (NULL, NULL, 'F');Kode ini berhasil diselesaikan.
Jalankan kode berikut untuk query T3FK:
PILIH * DARI dbo.T3FK;Anda mendapatkan output berikut:
id col1 col2 othercol unqpath----------- ----------- ----------- --------- - -----------------------2 1 100 A 1.1003 1 200 B 1.2004 3 NULL C 3.5 NULL 300 D .3006 NULL NULL E NULL7 NULL NULL F NULLJadi butuh sedikit kreativitas, tetapi sekarang Anda memiliki solusi untuk batasan unik standar, termasuk dukungan kunci asing.
Kesimpulan
Anda akan berpikir bahwa batasan unik adalah fitur yang mudah, tetapi ini bisa menjadi sedikit rumit ketika Anda perlu mendukung NULL di kolom unik. Itu menjadi lebih kompleks ketika Anda perlu menerapkan fungsionalitas batasan unik standar di T-SQL, karena keduanya menggunakan aturan yang berbeda dalam hal bagaimana mereka menangani NULL. Dalam artikel ini saya menjelaskan perbedaan antara keduanya dan solusi yang disediakan yang berfungsi di T-SQL. Anda dapat menggunakan indeks terfilter sederhana saat Anda perlu menerapkan keunikan hanya pada satu kolom NULLable, dan Anda tidak perlu mendukung kunci asing yang mereferensikan kolom itu. Namun, jika Anda perlu mendukung kunci asing atau batasan unik gabungan dengan fungsionalitas standar, Anda akan memerlukan implementasi yang lebih kompleks dengan kunci pengganti dan kolom yang dihitung.