Jak utworzyć unikalny indeks w kolumnie NULL?

101

Używam programu SQL Server 2005. Chcę ograniczyć wartości w kolumnie, aby były unikalne, jednocześnie zezwalając na wartości NULLS.

Moje obecne rozwiązanie obejmuje unikalny indeks takiego widoku:

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)

Jakieś lepsze pomysły?

Nuno G
źródło
16
nie ma szans na użycie sql 2008? możesz utworzyć filtrowany indeks używając 'gdzie'
Simon_Weaver
3
Nie miałeś na myśli unikalności, zezwalając na wartości NULL , wydaje się, że miałeś na myśli unikalność, ale włączając wiele wartości NULL . W przeciwnym razie NULL jest indeksowany jak każda inna wartość, a ograniczenie unikalności działa zgodnie z oczekiwaniami - tylko nie zgodnie ze standardami SQL, jak @pst wspomniano w komentarzu poniżej.
Suncat2000

Odpowiedzi:

26

Jestem prawie pewien, że nie możesz tego zrobić, ponieważ narusza to cel unikatów.

Jednak ta osoba wydaje się dobrze obejść: http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html

willasaywhat
źródło
2
Wygląda na to, że treść podanego przez Ciebie linku została faktycznie (częściowo) skopiowana bez
podania źródła
77
Nie zgadzam się, że „narusza to cel unikatów” - NULL to specjalna wartość w SQL (podobna pod wieloma względami do NaN) i należy ją odpowiednio traktować. W rzeczywistości jest to błąd w SQL Server w przestrzeganiu różnych specyfikacji SQL: tutaj jest łącze do żądania „poprawnej implementacji” tego, co jest warte: connect.microsoft.com/SQLServer/feedback/details/299229/… .
5
dla odniesienia w 2008 r. możesz zrobić STWÓRZ UNIKALNY INDEKS foo NA dbo.bar (klucz) GDZIE klucz NIE JEST NULL;
niico
2
Nie zgadzam się również z określeniem „narusza cel unikatów”, NULL nie równa się NULL, więc powinieneś być w stanie utworzyć unikalny indeks w kolumnie dopuszczającej wartość null i wstawiać wiele wartości null.
Wodzu
105

Korzystając z SQL Server 2008, można utworzyć filtrowany indeks: http://msdn.microsoft.com/en-us/library/cc280372.aspx . (Widzę, że Simon dodał to jako komentarz, ale pomyślałem, że zasługuje na własną odpowiedź, ponieważ komentarz łatwo przeoczyć.)

Inną opcją jest wyzwalacz do sprawdzenia niepowtarzalności, ale może to wpłynąć na wydajność.

Phil Haselden
źródło
84
create unique index UIX on MyTable (Column1) where Column1 is not null
Jørn Schou-Rode
1
Uwaga: obecnie SQL Server Management Studio nie wie, jak utworzyć takie indeksy, więc jeśli później zmodyfikujesz tabelę, zostanie zdezorientowana i spróbujesz ją usunąć, więc pamiętaj, aby ją odtworzyć
Simon_Weaver
3
Wygląda na to, że Microsoft zaktualizował SSMS, aby to obsługiwał. Mam SSMS 10.50.1617 iw oknie dialogowym Właściwości indeksu możesz wybrać stronę Filtr, aby edytować filtr. np. „([Column1] IS NOT NULL)”
Phil Haselden
5
Zezwolenie na wiele wartości null w indeksie i filtrowanie wartości null z indeksu to odrębne rzeczy. Filtrowanie indeksu faktycznie wyklucza rekordy z indeksu, podczas gdy inne rozwiązania przekształcają wartość null w użyteczną unikalną wartość. Bądź świadomy różnicy.
Suncat2000
Jeśli używasz procedur składowanych w tabeli z takim indeksem filtrowanym, upewnij się, że tak ANSI_NULLSjest ON, w przeciwnym razie podczas próby wstawienia danych wystąpi błąd.
Arne
71

Ta sztuczka z kolumną obliczeniową jest powszechnie znana jako „nullbuster”; moje notatki uznają 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
źródło
To wygląda na fajną sztuczkę. Dziwne wyszukiwanie nullbuster nie wywołuje zbyt wielu rzeczy. Zastanawiam się, czy przyda się to również do przyspieszenia wyszukiwania - zamiast obliczonej kolumny tylko 1 i 0 dla wartości null lub nie, czy użycie PK daje indeksowi coś więcej do pracy? Zamierzam przetestować ten weekend na dużym stole i zobaczyć.
David Storfer,
@DavidStorfer, nie możesz tego zrobić, ponieważ możesz mieć kolizję między identyfikatorami dwóch różnych tabel.
user393274
Ulepszenie: ISNULL (X, CONVERT (VARCHAR (10), pk))
Faiz
5
@Faiz: poprawa jest w oku patrzącego. Wolę wygląd oryginału.
onedaywhen
@NunoG, powinna to być akceptowana odpowiedź, ponieważ zapewnia dobre rozwiązanie zgodne z Twoimi wymaganiami, zamiast po prostu łączyć zewnętrzną witrynę, która może zniknąć.
Frédéric
-3

Ściśle mówiąc, unikatowa kolumna dopuszczająca wartość null (lub zestaw kolumn) może mieć wartość NULL (lub rekord wartości NULL) tylko raz, ponieważ posiadanie tej samej wartości (i obejmuje to NULL) więcej niż raz w oczywisty sposób narusza ograniczenie unikalności.

Nie oznacza to jednak, że koncepcja „unikatowych kolumn dopuszczających wartość null” jest prawidłowa; aby faktycznie zaimplementować go w dowolnej relacyjnej bazie danych, musimy tylko pamiętać, że tego rodzaju bazy danych mają być znormalizowane, aby poprawnie działać, a normalizacja zwykle obejmuje dodanie kilku (nie-encji) dodatkowych tabel w celu ustalenia relacji między jednostkami .

Spójrzmy na podstawowy przykład, biorąc pod uwagę tylko jedną „unikalną kolumnę dopuszczającą wartość null”, łatwo jest ją rozszerzyć na więcej takich kolumn.

Załóżmy, że mamy informacje reprezentowane przez taką tabelę:

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

Możemy to zrobić, oddzielając wartość uniqnull i dodając drugą tabelę, aby ustanowić relację między wartościami unikatowymi i the_entity (zamiast mieć unikalną wartość „wewnątrz” elementu 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)
);

Aby skojarzyć wartość uniqnull z wierszem w the_entity, musimy również dodać wiersz w the_relation.

Dla wierszy w the_entity, w których nie są skojarzone żadne unikalne wartości puste (tj. Dla tych, które umieścilibyśmy NULL w the_entity_incorrect), po prostu nie dodajemy wiersza w the_relation.

Zauważ, że wartości dla uniqnull będą unikalne dla całego the_relation, a także zauważ, że dla każdej wartości w the_entity może być co najwyżej jedna wartość w the_relation, ponieważ wymuszają to klucze podstawowy i obcy.

Następnie, jeśli wartość 5 dla unikqnull ma być skojarzona z identyfikatorem the_entity równym 3, musimy:

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

A jeśli wartość id równa 10 dla the_entity nie ma unikalnego odpowiednika, robimy tylko:

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

Aby zdenormalizować te informacje i uzyskać dane, które mogłaby zawierać tabela taka jak the_entity_incorrect, musimy:

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

Operator „left external join” zapewnia, że ​​w wyniku pojawią się wszystkie wiersze z the_entity, umieszczając NULL w unikatowej kolumnie, gdy w the_relation nie ma pasujących kolumn.

Pamiętaj, że każdy wysiłek poświęcony przez kilka dni (lub tygodni lub miesięcy) na zaprojektowanie dobrze znormalizowanej bazy danych (i odpowiednich denormalizujących poglądów i procedur) pozwoli Ci zaoszczędzić lata (lub dekady) bólu i zmarnowanych zasobów.

Roy
źródło
6
Jak już stwierdzono w komentarzu zaakceptowanej odpowiedzi z pięćdziesięcioma głosami za, MS Sql Server powinien obsługiwać go, aby mieć wiele wartości null w kolumnach indeksowanych jako unikalne. Nie można wdrożyć standardów SQL, aby na to nie pozwolić. Null nie jest wartością, null nie jest równe null, to jest podstawowa reguła SQL od lat. Więc twoje pierwsze zdanie jest błędne i większość czytelników nie będzie zawracać sobie głowy czytaniem.
Frédéric