Database
 sql >> Baza danych >  >> RDS >> Database

Złożoność NULL – Część 4, Brak standardowego unikalnego ograniczenia

Ten artykuł jest częścią 4 serii o NULL złożoności. W poprzednich artykułach (Część 1, Część 2 i Część 3) omówiłem znaczenie NULL jako znacznika brakującej wartości, jak zachowują się NULL w porównaniach i innych elementach zapytań oraz standardowe funkcje obsługi NULL, które nie są jeszcze dostępne w T-SQL. W tym miesiącu omawiam różnicę między sposobem definiowania ograniczenia unikatowego w standardzie ISO/IEC SQL a sposobem jego działania w T-SQL. Dostarczę również niestandardowe rozwiązania, które możesz wdrożyć, jeśli potrzebujesz standardowej funkcjonalności.

Standardowe ograniczenie UNIKALNE

SQL Server obsługuje wartości NULL, podobnie jak wartości inne niż NULL, w celu wymuszenia ograniczenia unikatowego. Oznacza to, że unikatowe ograniczenie na T jest spełnione wtedy i tylko wtedy, gdy nie istnieją dwa wiersze R1 i R2 z T, takie, że R1 i R2 mają tę samą kombinację wartości NULL i innych niż NULL w unikalnych kolumnach. Załóżmy na przykład, że definiujesz ograniczenie przez unikalność dla col1, która jest kolumną typu danych typu INT dopuszczającą NULL. Próba zmodyfikowania tabeli w sposób, który spowodowałby powstanie więcej niż jednego wiersza z wartością NULL w col1 zostanie odrzucona, podobnie jak modyfikacja, w wyniku której powstanie więcej niż jeden wiersz z wartością 1 w col1 zostanie odrzucona.

Załóżmy, że definiujesz złożone ograniczenie unikatowe dla kombinacji kolumn typu NULLable INT col1 i col2. Próba zmodyfikowania tabeli w sposób, który spowodowałby więcej niż jedno wystąpienie dowolnej z następujących kombinacji wartości (col1, col2) zostanie odrzucona:(NULL, NULL), (3, NULL), (NULL, 300 ), (1, 100).

Jak widać, implementacja ograniczenia unikalności w T-SQL traktuje wartości NULL tak samo jak wartości inne niż NULL w celu wymuszenia unikalności.

Jeśli chcesz zdefiniować klucz obcy w jakiejś tabeli X odwołującej się do tabeli Y, musisz wymusić unikalność w odniesieniu do kolumn(y), do których się odwołujesz za pomocą jednej z następujących opcji:

  • Klucz podstawowy
  • Unikalne ograniczenie
  • Niefiltrowany unikalny indeks

Klucz podstawowy nie jest dozwolony w kolumnach NULLable. Zarówno unikatowe ograniczenie (które tworzy indeks pod okładkami), jak i jawnie utworzony unikalny indeks są dozwolone w kolumnach NULLable i wymuszają ich unikalność w T-SQL przy użyciu wyżej wymienionej logiki. Tabela odniesienia może mieć wiersze z wartością NULL w kolumnie odniesienia, niezależnie od tego, czy tabela odniesienia ma wiersz z wartością NULL w kolumnie odniesienia. Chodzi o to, aby wspierać opcjonalny związek. Niektóre wiersze w tabeli odniesienia mogą być wierszami, które nie są powiązane z żadnymi wierszami w tabeli odniesienia. Zaimplementujesz to, używając wartości NULL w kolumnie odniesienia.

Aby zademonstrować implementację ograniczenia przez unikalność w języku T-SQL, uruchom następujący kod, który utworzy tabelę o nazwie T3 z ograniczeniem przez unikalność zdefiniowanym w kolumnie NULLable INT col1 i wypełni ją kilkoma przykładowymi wierszami:

USE tempdb;GO DROP TABLE IF EXISTS dbo.T3;GO CREATE TABLE dbo.T3(col1 INT NULL, col2 INT NULL, CONSTRAINT UNQ_T3 UNIQUE(col1)); INSERT INTO dbo.T3(col1, col2) VALUES(1, 100),(2,-1),(NULL,-1),(3,300);

Użyj następującego kodu do zapytania o tabelę:

WYBIERZ * Z dbo.T3;

To zapytanie generuje następujące dane wyjściowe:

kol1 kol2----------- -----------1 1002 -1NULL -13 300

Spróbuj wstawić drugi wiersz z wartością NULL w kolumnie 1:

WSTAW DO dbo.T3(col1, col2) VALUES(NULL, 400);

Ta próba zostaje odrzucona i pojawia się następujący błąd:

Msg 2627, poziom 14, stan 1
Naruszenie ograniczenia UNIQUE KEY „UNQ_T3”. Nie można wstawić zduplikowanego klucza w obiekcie „dbo.T3”. Zduplikowana wartość klucza to ().

Standardowa definicja ograniczenia unikalnego jest nieco inna niż wersja T-SQL. Główna różnica dotyczy obsługi wartości NULL. Oto unikalna definicja ograniczenia ze standardu:

„Unikalne ograniczenie T jest spełnione wtedy i tylko wtedy, gdy nie istnieją dwa wiersze R1 i R2 z T, takie, że R1 i R2 mają te same wartości różne od NULL w unikalnych kolumnach”.

Tak więc tabela T z unikalnym ograniczeniem dla col1 pozwoli na wiele wierszy z wartością NULL w col1, ale zabroni wielu wierszy z tą samą wartością inną niż NULL w col1.

Trudniejsze do wyjaśnienia jest to, co dzieje się zgodnie ze standardem ze złożonym ograniczeniem unikalnym. Powiedzmy, że masz zdefiniowane ograniczenie unikatowe (col1, col2). Możesz mieć wiele wierszy z (NULL, NULL), ale nie możesz mieć wielu wierszy z (3, NULL), tak jak nie możesz mieć wielu wierszy z (1, 100). Podobnie nie możesz mieć wielu wierszy z (NULL, 300). Chodzi o to, że nie możesz mieć wielu wierszy z tymi samymi wartościami innymi niż NULL w unikalnych kolumnach. Jeśli chodzi o klucz obcy, możesz mieć dowolną liczbę wierszy w tabeli odniesienia z wartościami NULL we wszystkich kolumnach odniesienia, niezależnie od tego, co istnieje w tabeli odniesienia. Takie wiersze nie są powiązane z żadnymi wierszami w tabeli, do której istnieje odwołanie (relacja opcjonalna). Jeśli jednak w którejkolwiek z kolumn, do których się odwołujesz, istnieje jakakolwiek wartość różna od NULL, w tabeli, do której się odwołujesz, musi istnieć wiersz z tymi samymi wartościami innymi niż NULL w kolumnach, do których się odwołujesz.

Załóżmy, że masz bazę danych na platformie obsługującej standardowe ograniczenie unikatowości i musisz przeprowadzić migrację tej bazy danych do programu SQL Server. Mogą wystąpić problemy z egzekwowaniem ograniczeń przez unikalność w programie SQL Server, jeśli unikatowe kolumny obsługują wartości NULL. Dane, które zostały uznane za prawidłowe w systemie źródłowym, mogą zostać uznane za nieprawidłowe w programie SQL Server. W kolejnych sekcjach omówię kilka możliwych obejść w SQL Server.

Rozwiązanie 1, przy użyciu filtrowanego indeksu lub widoku indeksowanego

Typowym obejściem w języku T-SQL w celu wymuszenia funkcji standardowego ograniczenia unikatowości, gdy w grę wchodzi tylko jedna kolumna docelowa, jest użycie unikatowego filtrowanego indeksu, który filtruje tylko wiersze, w których kolumna docelowa nie ma wartości NULL. Poniższy kod usuwa istniejące ograniczenie unikatowe z T3 i implementuje taki indeks:

ZMIEŃ TABELĘ dbo.T3 OGRANICZENIE UPUSZCZANIA UNQ_T3; UTWÓRZ UNIKALNY INDEKS NIESKLASTRAROWANY idx_col1_notnull NA dbo.T3(col1) GDZIE col1 NIE JEST NULL;

Ponieważ indeks filtruje tylko wiersze, w których col1 nie ma wartości NULL, jego właściwość UNIQUE jest wymuszana tylko dla wartości col1 innych niż NULL.

Przypomnij sobie, że T3 ma już wiersz z wartością NULL w col1. Aby przetestować to rozwiązanie, użyj następującego kodu, aby dodać drugi wiersz z wartością NULL w col1:

WSTAW DO dbo.T3(col1, col2) VALUES(NULL, 400);

Ten kod działa pomyślnie.

Przypomnij sobie, że T3 ma już wiersz o wartości 1 w col1. Uruchom następujący kod, aby spróbować dodać drugi wiersz z 1 w kol1:

WSTAW DO dbo.T3(col1, col2) VALUES (1, 500);

Zgodnie z oczekiwaniami ta próba kończy się niepowodzeniem z następującym błędem:

Msg 2601, poziom 14, stan 1
Nie można wstawić zduplikowanego wiersza klucza w obiekcie „dbo.T3” z unikatowym indeksem „idx_col1_notnull”. Zduplikowana wartość klucza to (1).

Użyj następującego kodu do zapytania T3:

WYBIERZ * Z dbo.T3;

Ten kod generuje następujące dane wyjściowe pokazujące dwa wiersze z wartością NULL w col1:

kol1 kol2----------- -----------1 1002 -1NULL -13 300NULL 400

To rozwiązanie działa dobrze, gdy musisz wymusić unikalność tylko w jednej kolumnie i gdy nie musisz wymuszać integralności referencyjnej za pomocą klucza obcego wskazującego na tę kolumnę.

Problem z kluczem obcym polega na tym, że SQL Server wymaga klucza podstawowego lub ograniczenia unikatowego lub unikatowego niefiltrowanego indeksu zdefiniowanego w kolumnie, do której istnieje odwołanie. Nie działa, gdy istnieje tylko unikalny filtrowany indeks zdefiniowany w kolumnie, do której istnieje odwołanie. Spróbujmy utworzyć tabelę z kluczem obcym odwołującym się do T3.col1. Najpierw użyj następującego kodu, aby utworzyć tabelę T3:

DROP TABLE IF EXISTS dbo.T3FK;GO CREATE TABLE dbo.T3FK(id INT NOT NULL OGRANICZENIE TOŻSAMOŚCI PK_T3FK PODSTAWOWY KLUCZ, col1 INT NULL, col2 INT NULL, innycol VARCHAR(10) NOT NULL);

Następnie spróbuj uruchomić następujący kod, próbując dodać klucz obcy wskazujący z T3FK.col1 na T3.col1:

ZMIEŃ TABELĘ dbo.T3FK DODAJ OGRANICZENIE FK_T3_T3FK KLUCZ OBCY(col1) REFERENCES dbo.T3(col1);

Ta próba kończy się niepowodzeniem z następującym błędem:

Msg 1776, poziom 16, stan 0
W tabeli odniesienia „dbo.T3” nie ma kluczy podstawowych ani kandydujących, które pasują do listy kolumn odniesienia w kluczu obcym „FK_T3_T3FK”.

Msg 1750, Poziom 16, Stan 1
Nie można utworzyć ograniczenia lub indeksu. Zobacz poprzednie błędy.

W tym momencie upuść istniejący filtrowany indeks w celu oczyszczenia:

DROP INDEX idx_col1_notnull NA dbo.T3;

Nie upuszczaj tabeli T3FK, ponieważ użyjesz jej w późniejszych przykładach.

Innym problemem związanym z rozwiązaniem z filtrowanym indeksem, przy założeniu, że nie jest potrzebny klucz obcy, jest to, że nie działa ono, gdy trzeba wymusić standardową funkcjonalność ograniczenia unikatowego na wielu kolumnach, na przykład w kombinacji (col1, col2) . Pamiętaj, że standardowe ograniczenie unikatowości nie zezwala na duplikowanie kombinacji wartości innych niż NULL w unikalnych kolumnach. Aby zaimplementować tę logikę z filtrowanym indeksem, należy filtrować tylko wiersze, w których żadna z unikatowych kolumn nie ma wartości NULL. Inaczej mówiąc, musisz filtrować tylko wiersze, które nie mają wartości NULL we wszystkich unikalnych kolumnach. Niestety filtrowane indeksy dopuszczają tylko bardzo proste wyrażenia. Nie obsługują OR, NOT ani manipulacji na kolumnach. Dlatego żadna z poniższych definicji indeksów nie jest obecnie obsługiwana:

UTWÓRZ UNIKALNY INDEKS NIEKLASTROWANY idx_customunique NA dbo.T3(col1, col2) GDZIE col1 NIE JEST NULL LUB col2 NIE JEST NULL; UTWÓRZ UNIKALNY INDEKS BEZKLASTOWY idx_customunique NA dbo.T3(col1, col2) GDZIE NIE (col1 JEST NULL, A col2 JEST NULL); UTWÓRZ UNIKALNY INDEKS BEZKLASTOWY idx_customunique ON dbo.T3(col1, col2) GDZIE COALESCE(col1, col2) NIE JEST NULL;

Obejściem w takim przypadku jest utworzenie indeksowanego widoku na podstawie zapytania, które zwraca col1 i col2 z T3 z jedną z powyższych klauzul WHERE, z unikalnym indeksem klastrowym on (col1, col2), na przykład:

UTWÓRZ WIDOK dbo.T3CustomUnique Z POWIĄZANIAMI SCHEMATÓW WYBIERZ col1, col2 Z dbo.T3 GDZIE col1 NIE JEST NULL LUB col2 NIE JEST NULL; PRZEJDŹ UTWÓRZ UNIKALNY SKLASTROWANY INDEKS idx_col1_col2 NA dbo.T3CustomUnique2); GO. 

Będziesz mógł dodać wiele wierszy z (NULL, NULL) w (col1, col2), ale nie będziesz mógł dodawać wielu wystąpień różnych kombinacji wartości w (col1, col2), takich jak (3 , NULL) lub (NULL, 300) lub (1, 100). Mimo to to rozwiązanie nie obsługuje klucza obcego.

W tym momencie uruchom następujący kod do czyszczenia:

DRUP VIEW, JEŚLI ISTNIEJE dbo.T3CustomUnique;

Rozwiązanie 2, przy użyciu klucza zastępczego i kolumny obliczeniowej

Rozwiązania z filtrowanym indeksem i widokiem indeksowanym są dobre, o ile nie musisz obsługiwać klucza obcego. Ale co, jeśli musisz wymusić integralność referencyjną? Jedną z opcji jest dalsze używanie filtrowanego indeksu lub rozwiązania widoku indeksowanego w celu wymuszenia unikalności i użycie wyzwalaczy w celu wymuszenia integralności referencyjnej. Ta opcja jest jednak dość droga.

Inną opcją jest użycie zupełnie innego rozwiązania dla części unikatowości, która obsługuje klucz obcy. Rozwiązanie polega na dodaniu dwóch kolumn do tabeli odniesienia (w naszym przypadku T3). Jedna kolumna o nazwie id jest kluczem zastępczym z właściwością tożsamości. Inna kolumna o nazwie flaga jest utrwaloną kolumną obliczaną, która zwraca id, gdy col1 ma wartość NULL i 0, gdy nie ma wartości NULL. Następnie wymuszasz unikatowe ograniczenie kombinacji col1 i flag. Oto kod do dodania dwóch kolumn i ograniczenia unikalności:

ALTER TABELA dbo.T3 ADD id INT NOT NULL IDENTITY, flag as case GDY col1 IS NULL THEN id ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3_col1_flag UNIQUE(col1, flaga);

Użyj następującego kodu do zapytania T3:

WYBIERZ * Z dbo.T3;

Ten kod generuje następujące dane wyjściowe:

flaga identyfikatora kol1 kol2 ------------ ----------- ----------- ---------- -1 100 1 02 -1 2 0Brak -1 3 33 300 4 0Brak 400 5 5

Jeśli chodzi o tabelę referencyjną (w naszym przypadku T3FK), dodajesz wyliczoną kolumnę o nazwie flag, która zawsze jest ustawiona na 0, oraz klucz obcy zdefiniowany na (col1, flag) wskazujący na unikalne kolumny T3 (col1, flag), tak :

ZMIEŃ TABELĘ dbo.T3FK DODAJ flagę AS 0 PERSISTED, OGRANICZENIE FK_T3_T3FK FOREIGN KEY(col1, flag) REFERENCES dbo.T3(col1, flag);

Przetestujmy to rozwiązanie.

Spróbuj dodać następujące wiersze:

WSTAW DO dbo.T3FK(col1, col2, othercol) WARTOŚCI (1, 100, 'A'), (2, -1, 'B'), (3, 300, 'C');

Te wiersze zostały dodane pomyślnie, tak jak powinny, ponieważ wszystkie mają odpowiadające sobie wiersze.

Zapytanie do tabeli T3FK:

WYBIERZ * Z dbo.T3FK;

Otrzymasz następujące dane wyjściowe:

id col1 col2 innycol flag----------- ----------- ----------- --------- ------------1 1 100 A 02 2 -1 B 03 3 300 C 0

Spróbuj dodać wiersz, który nie ma odpowiadającego mu wiersza w tabeli odniesienia:

WSTAW DO dbo.T3FK(col1, col2, othercol) WARTOŚCI (4, 400, 'D');

Próba jest odrzucana, tak jak powinna, z następującym błędem:

Msg 547, poziom 16, stan 0
Instrukcja INSERT była w konflikcie z ograniczeniem klucza obcego „FK_T3_T3FK”. Konflikt wystąpił w bazie danych „TSQLV5”, tabeli „dbo.T3”.

Spróbuj dodać wiersz do T3FK z NULL w kol1:

WSTAW DO dbo.T3FK(col1, col2, othercol) WARTOŚCI (NULL, NULL, 'E');

Uważa się, że ten wiersz nie jest powiązany z żadnym wierszem w T3FK (relacja opcjonalna) i zgodnie ze standardem powinien być dozwolony niezależnie od tego, czy w tabeli odniesienia w col1 istnieje wartość NULL. T-SQL obsługuje ten scenariusz, a wiersz został pomyślnie dodany.

Zapytanie do tabeli T3FK:

WYBIERZ * Z dbo.T3FK;

Ten kod generuje następujące dane wyjściowe:

id col1 col2 innycol flag----------- ----------- ----------- --------- - -----------1 1 100 A 02 2 -1 B 03 3 300 C 05 NULL NULL E 0

Rozwiązanie sprawdza się dobrze, gdy trzeba wymusić standardową funkcjonalność unikatowości na pojedynczej kolumnie. Ale ma problem, gdy trzeba wymusić unikalność w wielu kolumnach. Aby zademonstrować problem, najpierw usuń tabele T3 i T3FK:

DROP TABELA, JEŚLI ISTNIEJE dbo.T3FK, dbo.T3;

Użyj następującego kodu, aby odtworzyć T3 z włączonym złożonym ograniczeniem unikalności (col1, col2, flag):

CREATE TABLE dbo.T3( col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, flaga JAKO PRZYPADEK GDY col1 JEST NULL I col2 JEST NULL TO id ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE(col1, col2, flaga ));

Zauważ, że flaga jest ustawiona na id, gdy col1 i col2 mają wartości NULL, a 0 w przeciwnym razie.

Samo unikatowe ograniczenie działa dobrze.

Uruchom następujący kod, aby dodać kilka wierszy do T3, w tym wielokrotne wystąpienia (NULL, NULL) w (col1, col2):

WSTAW DO dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);

Te wiersze zostały pomyślnie dodane, tak jak powinny.

Spróbuj dodać dwa wystąpienia (1, NULL) w (col1, col2):

WSTAW DO dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);

Ta próba kończy się niepowodzeniem, jak powinna, z następującym błędem:

Msg 2627, poziom 14, stan 1
Naruszenie ograniczenia UNIQUE KEY „UNQ_T3”. Nie można wstawić zduplikowanego klucza w obiekcie „dbo.T3”. Zduplikowana wartość klucza to (1, , 0).

Spróbuj dodać dwa wystąpienia (NULL, 100) w (col1, col2):

WSTAW DO dbo.T3(col1, col2) VALUES(NULL, 100),(NULL, 100);

Ta próba również kończy się niepowodzeniem, tak jak powinna, z następującym błędem:

Msg 2627, poziom 14, stan 1
Naruszenie ograniczenia UNIQUE KEY „UNQ_T3”. Nie można wstawić zduplikowanego klucza w obiekcie „dbo.T3”. Zduplikowana wartość klucza to (, 100, 0).

Spróbuj dodać następujące dwa wiersze, w których nie powinno dojść do naruszenia:

WSTAW DO dbo.T3(col1, col2) VALUES(3, NULL),(NULL, 300);

Te wiersze zostały pomyślnie dodane.

Zapytaj tabelę T3 w tym momencie:

WYBIERZ * Z dbo.T3;

Otrzymasz następujące dane wyjściowe:

flaga identyfikatora kol1 kol2 ------------ ----------- ----------- ---------- -1 100 1 01 200 2 0PUSTY BRAK 3 3PUSTY BRAK 4 43 BRAK 9 0PUSTY 300 10 0

Jak dotąd tak dobrze.

Następnie uruchom następujący kod, aby utworzyć tabelę T3FK ze złożonym kluczem obcym odwołującym się do unikalnych kolumn T3:

CREATE TABLE dbo.T3FK( id INT NOT NULL IDENTITY CONSTRAINT PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, innecol VARCHAR(10) NOT NULL, flag AS 0 PERSISTED, CONSTRAINT FK_T3_T3FK flag(col1, col col ). ) ODNIESIENIA dbo.T3(col1, col2, flag));

To rozwiązanie naturalnie umożliwia dodawanie wierszy do T3FK z (NULL, NULL) w (col1, col2). Problem polega na tym, że umożliwia również dodawanie wierszy o wartości NULL w col1 lub col2, nawet jeśli druga kolumna nie ma wartości NULL, a przywoływana tabela T3 nie ma takiej kombinacji klawiszy. Na przykład spróbuj dodać następujący wiersz do T3FK:

INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');

Ten wiersz został pomyślnie dodany, mimo że w T3 nie ma powiązanego wiersza. Zgodnie ze standardem ten wiersz nie powinien być dozwolony.

Powrót do deski kreślarskiej…

Rozwiązanie 3, przy użyciu klucza zastępczego i kolumny obliczeniowej

Problem z poprzednim rozwiązaniem (Rozwiązanie 2) pojawia się, gdy trzeba obsługiwać złożony klucz obcy. Pozwala na wiersze w tabeli odwołującej, które mają wartość NULL na liście w jednej kolumnie odwołującej, nawet jeśli w innych kolumnach odwołujących się znajdują wartości inne niż NULL, a w tabeli odwołującej się nie ma powiązanego wiersza. Aby rozwiązać ten problem, możesz użyć odmiany poprzedniego rozwiązania, które nazwiemy Rozwiązaniem 3.

Najpierw użyj następującego kodu, aby usunąć istniejące tabele:

DROP TABELA, JEŚLI ISTNIEJE dbo.T3FK, dbo.T3;

W nowym rozwiązaniu w tabeli, do której istnieje odwołanie (w naszym przypadku T3), nadal używasz kolumny klucza zastępczego identyfikatora opartego na tożsamości. Używasz również utrwalonej kolumny wyliczanej o nazwie unqpath. Kiedy wszystkie unikalne kolumny (col1 i col2 w naszym przykładzie) mają wartość NULL, ustawiasz unqpath jako ciąg znaków reprezentujący id (bez separatorów ). Jeśli dowolna z unikalnych kolumn nie ma wartości NULL, ustawiasz unqpath jako ciąg znaków reprezentujący oddzieloną listę unikalnych wartości kolumn za pomocą funkcji CONCAT. Ta funkcja zastępuje NULL pustym ciągiem. Ważne jest, aby upewnić się, że używasz separatora, który normalnie nie może pojawić się w samych danych. Na przykład przy wartościach całkowitych col1 i col2 masz tylko cyfry, więc zadziałałby każdy separator inny niż cyfra. W moim przykładzie użyję kropki (.). Następnie wymuszasz unikatowe ograniczenie na unqpath. Nigdy nie będziesz mieć konfliktu między wartością unqpath, gdy wszystkie unikalne kolumny mają wartość NULL (ustawioną na id), a gdy którakolwiek z unikalnych kolumn nie ma wartości NULL, ponieważ w pierwszym przypadku unqpath nie zawiera separatora, a w drugim przypadku . Pamiętaj, że będziesz używać Rozwiązania 3, gdy masz klucz złożony, i prawdopodobnie wolisz Rozwiązanie 2, które jest prostsze, gdy masz klucz jednokolumnowy. Jeśli chcesz użyć rozwiązania 3 również z kluczem jednokolumnowym, a nie rozwiązania 2, po prostu upewnij się, że dodajesz separator, gdy unikatowa kolumna nie ma wartości NULL, nawet jeśli dotyczy tylko jednej wartości. W ten sposób nie będziesz mieć konfliktu, gdy id w wierszu, w którym col1 jest równe NULL, jest równe col1 w innym wierszu, ponieważ pierwszy nie będzie miał separatora, a drugi będzie.

Oto kod do stworzenia T3 z wyżej wymienionymi dodatkami:

CREATE TABLE dbo.T3( col1 INT NULL, col2 INT NULL, id INT NOT NULL TOŻSAMOŚĆ, unqpath JAK W PRZYPADKU JEŚLI col1 JEST NULL I col2 JEST NULL THEN CAST(id AS VARCHAR(10)) ELSE CONCAT(CAST(col1 AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE(unqpath));

Zanim zajmiemy się kluczem obcym i tabelą odniesienia, przetestujmy ograniczenie unikalności. Pamiętaj, że ma to uniemożliwić zduplikowane kombinacje wartości innych niż NULL w unikalnych kolumnach, ale ma pozwolić na wielokrotne wystąpienia wszystkich wartości NULL w unikalnych kolumnach.

Uruchom następujący kod, aby dodać kilka wierszy, w tym dwa wystąpienia (NULL, NULL) w (col1, col2):

WSTAW DO dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);

Ten kod kończy się pomyślnie, tak jak powinien.

Spróbuj dodać dwa wystąpienia (1, NULL) w (col1, col2):

WSTAW DO dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);

Ten kod kończy się niepowodzeniem z następującym błędem, tak jak powinien:

Msg 2627, poziom 14, stan 1
Naruszenie ograniczenia UNIQUE KEY „UNQ_T3”. Nie można wstawić zduplikowanego klucza w obiekcie „dbo.T3”. Zduplikowana wartość klucza to (1.).

Podobnie odrzucana jest następująca próba:

WSTAW DO dbo.T3(col1, col2) VALUES(NULL, 100),(NULL, 100);

Pojawia się następujący błąd:

Msg 2627, poziom 14, stan 1
Naruszenie ograniczenia UNIQUE KEY „UNQ_T3”. Nie można wstawić zduplikowanego klucza w obiekcie „dbo.T3”. Zduplikowana wartość klucza to (0,100).

Uruchom następujący kod, aby dodać kilka dodatkowych wierszy:

WSTAW DO dbo.T3(col1, col2) VALUES(3, NULL),(NULL, 300);

Ten kod działa poprawnie, tak jak powinien.

W tym momencie zapytaj T3:

WYBIERZ * Z dbo.T3;

Otrzymasz następujące dane wyjściowe:

id kol1 kol2 unqpath----------- ----------- ----------- ---------- -------------1 100 1 1.1001 200 2 1.200 PUDEŁKO 3 3 PUSTE PUDEŁKO 4 43 PUDEŁKO 9 3. PUDEŁKO 300 10 .300

Obserwuj wartości unqpath i upewnij się, że rozumiesz logikę ich konstrukcji oraz różnicę między przypadkiem, w którym wszystkie unikatowe kolumny mają wartość NULL (brak separatora), a przypadkiem, w którym co najmniej jedna nie jest NULL (istnieje separator).

Jeśli chodzi o tabelę referencyjną, T3FK; definiujesz również kolumnę wyliczaną o nazwie unqpath, ale w przypadku, gdy wszystkie kolumny odniesienia mają wartość NULL, ustawiasz kolumnę na NULL, a nie na id. Jeśli którakolwiek z kolumn odniesienia nie ma wartości NULL, tworzysz taką samą oddzieloną listę wartości, jak w T3. Następnie definiujesz klucz obcy na T3FK.unqpath wskazujący na T3.unqpath, na przykład:

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, GDY col1 JEST NULL, A col2 NULL THENULL (CAST(col1 AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) KONIEC UTRZYMANY, OGRANICZENIE FK_T3_T3FK KLUCZ OBCY (unqpath) REFERENCES dbo.T3(unqpath));

Ten klucz obcy odrzuci wiersze w T3FK, w których dowolna z odwołujących się kolumn nie ma wartości NULL, a w tabeli T3, do której się odwołuje, nie ma żadnego powiązanego wiersza, co pokazuje następująca próba:

INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');

Ten kod generuje następujący błąd:

Msg 547, poziom 16, stan 0
Instrukcja INSERT była w konflikcie z ograniczeniem klucza obcego „FK_T3_T3FK”. Konflikt wystąpił w bazie danych „TSQLV5”, tabeli „dbo.T3”, kolumnie „unqpath”.

To rozwiązanie spowoduje wyświetlenie wierszy w T3FK, w których żadna z odwołujących się kolumn nie ma wartości NULL, o ile istnieje powiązany wiersz w T3, a także wiersze z wartościami NULL we wszystkich odwołujących się kolumnach, ponieważ takie wiersze są uważane za niepowiązane z żadnymi wierszami w T3. Poniższy kod dodaje takie prawidłowe wiersze do T3FK:

INSERT INTO dbo.T3FK(col1, col2, othercol) WARTOŚCI (1 , 100 , 'A'), (1 , 200 , 'B'), (3 , NULL, 'C'), (NULL, 300 , 'D'), (NULL, NULL, 'E'), (NULL, NULL, 'F');

Ten kod został pomyślnie ukończony.

Uruchom następujący kod, aby wysłać zapytanie do T3FK:

WYBIERZ * Z dbo.T3FK;

Otrzymasz następujące dane wyjściowe:

id col1 col2 innycol unqpath----------- ----------- ----------- --------- - -----------------------2 1 100 A 1.1003 1 200 B 1.2004 3 zero C 3.5 zero 300 D .3006 zero zero E zero 7 zero F zero 

Zajęło to trochę kreatywności, ale teraz masz obejście dla standardowego ograniczenia unikatowego, w tym obsługę kluczy obcych.

Wniosek

Można by pomyśleć, że unikatowe ograniczenie jest prostą funkcją, ale może być trochę trudne, gdy trzeba obsługiwać wartości NULL w unikalnych kolumnach. Sprawa staje się bardziej złożona, gdy trzeba zaimplementować standardową funkcjonalność ograniczenia unikatowego w T-SQL, ponieważ oba używają różnych reguł w zakresie obsługi wartości NULL. W tym artykule wyjaśniłem różnicę między tymi dwoma i dostarczonymi obejściami, które działają w T-SQL. Możesz użyć prostego indeksu filtrowanego, gdy musisz wymusić unikalność tylko jednej kolumny z wartością NULL i nie musisz obsługiwać klucza obcego, który odwołuje się do tej kolumny. Jeśli jednak potrzebujesz obsługi klucza obcego lub złożonego ograniczenia unikalności ze standardową funkcjonalnością, będziesz potrzebować bardziej złożonej implementacji z kluczem zastępczym i kolumną obliczeniową.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Zatrudnij lub zdobądź zatrudnienie:model danych dla procesu rekrutacji

  2. Odkrywanie testów jednostkowych Java za pomocą JUnit Test Framework

  3. Podstawowe polecenia SQL:jak pisać proste zapytania z przykładami

  4. Wprowadzenie do statystyk oczekiwania

  5. N-ta najwyższa pensja