Bagaimana cara membuat indeks unik pada kolom NULL?

101

Saya menggunakan SQL Server 2005. Saya ingin membatasi nilai dalam kolom menjadi unik, sambil mengizinkan NULLS.

Solusi saya saat ini melibatkan indeks unik pada tampilan seperti ini:

CREATE VIEW vw_unq WITH SCHEMABINDING AS
    SELECT Column1
      FROM MyTable
     WHERE Column1 IS NOT NULL

CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)

Ada ide yang lebih baik?

Nuno G
sumber
16
tidak ada kesempatan untuk menggunakan sql 2008? Anda dapat membuat indeks yang difilter menggunakan 'where'
Simon_Weaver
3
Anda tidak bermaksud unik, mengizinkan NULL , yang Anda maksud sepertinya unik, tetapi menyertakan beberapa NULL . Jika tidak, NULL diindeks seperti nilai lainnya dan batasan keunikan berfungsi seperti yang diharapkan - hanya saja tidak sesuai dengan standar SQL, seperti yang disebutkan @pst dalam komentar di bawah.
Suncat2000

Jawaban:

26

Cukup yakin Anda tidak bisa melakukan itu, karena itu melanggar tujuan unik.

Namun, orang ini tampaknya memiliki solusi yang layak: http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html

akan mengatakan apa
sumber
2
Tampaknya konten tautan yang Anda berikan sebenarnya (sebagian) disalin tanpa atribusi dari sini: decipherinfosys.wordpress.com/2007/11/30/…
Tom Juergens
77
Saya tidak setuju bahwa itu "melanggar tujuan unik" - NULL adalah nilai khusus dalam SQL (mirip dalam banyak hal dengan NaN) dan perlu diperlakukan sesuai. Sebenarnya ini adalah kegagalan dalam SQL Server untuk menghormati berbagai spesifikasi SQL: berikut adalah tautan untuk permintaan "implementasi yang benar" untuk apa nilainya: connect.microsoft.com/SQLServer/feedback/details/299229/… .
5
untuk referensi di tahun 2008 anda dapat melakukan BUAT INDEKS UNIK foo ON dbo.bar (key) WHERE key IS NOT NULL;
niico
2
Saya tidak setuju juga dengan "melanggar tujuan unik", NULL tidak sama dengan NULL, jadi Anda harus dapat membuat indeks unik pada kolom nullable dan memasukkan beberapa nulls.
Wodzu
105

Menggunakan SQL Server 2008, Anda dapat membuat indeks yang difilter: http://msdn.microsoft.com/en-us/library/cc280372.aspx . (Saya melihat Simon menambahkan ini sebagai komentar, tetapi saya pikir itu pantas mendapatkan jawabannya sendiri karena komentar itu mudah terlewat.)

Pilihan lain adalah pemicu untuk memeriksa keunikan, tetapi ini dapat memengaruhi kinerja.

Phil Haselden
sumber
84
create unique index UIX on MyTable (Column1) where Column1 is not null
Jørn Schou-Rode
1
Catatan: saat ini SQL Server Management Studio sepertinya tidak tahu cara membuat indeks seperti itu jadi jika Anda nanti memodifikasi tabel, akan bingung dan mencoba untuk menjatuhkannya jadi ingatlah untuk membuatnya kembali
Simon_Weaver
3
Tampaknya Microsoft telah memperbarui SSMS untuk mendukung ini. Saya memiliki SSMS 10.50.1617 dan dalam dialog Properti Indeks Anda dapat memilih halaman Filter untuk mengedit filter. misalnya "([Column1] IS NOT NULL)"
Phil Haselden
5
Membiarkan beberapa null dalam indeks dan memfilter null dari indeks adalah hal yang terpisah. Memfilter indeks sebenarnya mengecualikan rekaman dari indeks, sedangkan solusi lain mengubah null menjadi nilai unik yang berguna. Waspadai perbedaannya.
Suncat2000
Jika Anda menggunakan prosedur yang tersimpan di atas meja dengan indeks disaring seperti itu, pastikan bahwa ANSI_NULLSadalah ON, jika tidak Anda akan mendapatkan error ketika mencoba untuk memasukkan data.
Arne
71

Trik kolom terhitung secara luas dikenal sebagai "nullbuster"; catatan saya memuji Steve Kass:

CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X  int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)
onedaywhen
sumber
Ini terlihat seperti trik yang keren. Anehnya, mencari nullbuster tidak menghasilkan terlalu banyak barang. Saya bertanya-tanya apakah ini akan berguna untuk mempercepat pencarian juga - daripada kolom yang dihitung hanya 1 dan 0 untuk null atau tidak, jika menggunakan PK memberikan indeks sesuatu yang lebih untuk dikerjakan? Akan menguji akhir pekan ini di meja besar dan melihat.
David Storfer
@DavidStorfer, Anda tidak dapat melakukan itu karena Anda dapat mengalami benturan antara ID dari dua tabel yang berbeda.
pengguna393274
Perbaikan: ISNULL (X, CONVERT (VARCHAR (10), pk))
Faiz
5
@Faiz: Peningkatan ada di mata yang melihatnya. Saya lebih suka tampilan aslinya.
onedaywhen
@NunoG, ini harus menjadi jawaban yang diterima karena ini memberikan solusi yang baik sesuai dengan kebutuhan Anda, daripada hanya menautkan situs eksternal yang mungkin hilang.
Frédéric
-3

Sebenarnya, kolom unik nullable (atau kumpulan kolom) dapat berupa NULL (atau catatan NULL) hanya sekali, karena memiliki nilai yang sama (dan ini termasuk NULL) lebih dari sekali jelas melanggar batasan unik.

Namun, itu tidak berarti konsep "kolom nullable unik" valid; untuk benar-benar menerapkannya dalam database relasional apa pun, kita hanya perlu mengingat bahwa jenis database ini dimaksudkan untuk dinormalisasi agar berfungsi dengan baik, dan normalisasi biasanya melibatkan penambahan beberapa tabel tambahan (non-entitas) untuk membangun hubungan antara entitas. .

Mari bekerja contoh dasar dengan mempertimbangkan hanya satu "kolom nullable unik", mudah untuk memperluasnya ke lebih banyak kolom seperti itu.

Misalkan kita informasi direpresentasikan oleh tabel seperti ini:

create table the_entity_incorrect
(
  id integer,
  uniqnull integer null, /* we want this to be "unique and nullable" */
  primary key (id)
);

Kita dapat melakukannya dengan memisahkan uniqnull dan menambahkan tabel kedua untuk membuat hubungan antara nilai uniqnull dan the_entity (daripada memiliki uniqnull "di dalam" the_entity):

create table the_entity
(
  id integer,
  primary key(id)
);

create table the_relation
(
  the_entity_id integer not null,
  uniqnull integer not null,

  unique(the_entity_id),
  unique(uniqnull),
  /* primary key can be both or either of the_entity_id or uniqnull */
  primary key (the_entity_id, uniqnull), 
  foreign key (the_entity_id) references the_entity(id)
);

Untuk mengaitkan nilai uniqnull ke baris di the_entity kita juga perlu menambahkan baris di the_relation.

Untuk baris di the_entity tidak ada nilai uniqnull yang diasosiasikan (yaitu untuk baris yang akan kita masukkan NULL di the_entity_incorrect) kita tidak menambahkan baris di the_relation.

Perhatikan bahwa nilai uniqnull akan unik untuk semua the_relation, dan juga perhatikan bahwa untuk setiap nilai dalam the_entity paling banyak dapat ada satu nilai dalam the_relation, karena kunci utama dan asing di atasnya memberlakukan ini.

Kemudian, jika nilai 5 untuk uniqnull akan dikaitkan dengan id the_entity dari 3, kita perlu:

start transaction;
insert into the_entity (id) values (3); 
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;

Dan, jika nilai id 10 untuk the_entity tidak memiliki pasangan uniqnull, kita hanya melakukan:

start transaction;
insert into the_entity (id) values (10); 
commit;

Untuk mendenormalisasi informasi ini dan mendapatkan data tabel seperti the_entity_incorrect, kita perlu:

select
  id, uniqnull
from
  the_entity left outer join the_relation
on
  the_entity.id = the_relation.the_entity_id
;

Operator "left outer join" memastikan semua baris dari the_entity akan muncul dalam hasil, meletakkan NULL di kolom uniqnull saat tidak ada kolom yang cocok di the_relation.

Ingat, setiap upaya yang dilakukan selama beberapa hari (atau minggu atau bulan) dalam mendesain database yang dinormalisasi dengan baik (dan tampilan dan prosedur denormalisasi yang sesuai) akan menghemat waktu bertahun-tahun (atau dekade) dari rasa sakit dan sumber daya yang terbuang percuma.

roy
sumber
6
Seperti yang telah dinyatakan pada komentar jawaban yang diterima dengan lima puluh suara positif, itu harus didukung oleh MS Sql Server untuk memiliki beberapa null dalam kolom yang diindeks sebagai unik. Jika tidak memungkinkan, penerapan standar SQL adalah kegagalan. Null bukan nilai, null tidak sama dengan null, itu adalah aturan SQL dasar sejak bertahun-tahun. Jadi kalimat pertama Anda salah dan kebanyakan pembaca tidak akan repot-repot membaca.
Frédéric