Den här artikeln är del 4 i en serie om NULL-komplexitet. I de tidigare artiklarna (del 1, del 2 och del 3) behandlade jag innebörden av NULL som en markör för ett saknat värde, hur NULL beter sig i jämförelser och i andra frågeelement och standard NULL-hanteringsfunktioner som inte är ännu tillgänglig i T-SQL. Den här månaden tar jag upp skillnaden mellan hur en unik begränsning definieras i ISO/IEC SQL-standarden och hur den fungerar i T-SQL. Jag kommer också att tillhandahålla skräddarsydda lösningar som du kan implementera om du behöver standardfunktionaliteten.
Standard UNIK begränsning
SQL Server hanterar NULLs precis som icke-NULL-värden i syfte att upprätthålla en unik begränsning. Det vill säga, en unik begränsning på T är uppfylld om och endast om det inte finns två rader R1 och R2 i T så att R1 och R2 har samma kombination av NULL-värden och icke-NULL-värden i de unika kolumnerna. Anta till exempel att du definierar en unik restriktion på col1, som är en NULLbar kolumn av en INT-datatyp. Ett försök att modifiera tabellen på ett sätt som skulle resultera i mer än en rad med en NULL i col1 kommer att avvisas, precis som en modifiering som skulle resultera i mer än en rad med värdet 1 i col1 kommer att avvisas.
Anta att du definierar en sammansatt unik begränsning för kombinationen av NULL-bara INT-kolumner col1 och col2. Ett försök att modifiera tabellen på ett sätt som skulle resultera i mer än en förekomst av någon av följande kombinationer av (col1, col2) värden kommer att avvisas:(NULL, NULL), (3, NULL), (NULL, 300) ), (1, 100).
Så som du kan se behandlar T-SQL-implementeringen av den unika begränsningen NULLs precis som icke-NULL-värden i syfte att upprätthålla unikhet.
Om du vill definiera en främmande nyckel på någon tabell X som refererar till någon tabell Y, måste du framtvinga unikhet på den eller de refererade kolumnerna med ett av följande alternativ:
- Primär nyckel
- Unik begränsning
- Ofiltrerat unikt index
En primärnyckel är inte tillåten på NULL-bara kolumner. Både en unik restriktion (som skapar ett index under omslagen) och ett uttryckligen skapat unikt index är tillåtna på NULL-kolumner, och framtvingar deras unika karaktär i T-SQL med den tidigare nämnda logiken. Referenstabellen tillåts ha rader med en NULL i referenskolumnen, oavsett om den refererade tabellen har en rad med en NULL i den refererade kolumnen. Tanken är att stödja ett valfritt förhållande. Vissa rader i referenstabellen kan vara sådana som inte är relaterade till några rader i den refererade tabellen. Du implementerar detta genom att använda en NULL i referenskolumnen.
För att demonstrera T-SQL-implementeringen av en unik begränsning, kör följande kod, som skapar en tabell som heter T3 med en unik begränsning definierad på NULLable INT-kolumnen col1, och fyller den med några exempelrader:
ANVÄND tempdb;GO SLIP TABELL OM FINNS dbo.T3;GO SKAPA TABELL 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);
Använd följande kod för att fråga tabellen:
VÄLJ * FRÅN dbo.T3;
Den här frågan genererar följande utdata:
col1 col2----------- -----------1 1002 -1NULL -13 300
Försök att infoga en andra rad med en NULL i kol1:
INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 400);
Detta försök avvisas och du får följande felmeddelande:
Msg 2627, Level 14, State 1Brott mot UNIQUE KEY-restriktionen 'UNQ_T3'. Kan inte infoga dubblettnyckel i objektet 'dbo.T3'. Dubblettnyckelvärdet är (
Standarddefinitionen av unika begränsningar är lite annorlunda än T-SQL-versionen. Den största skillnaden har att göra med NULL-hanteringen. Här är den unika begränsningsdefinitionen från standarden:
"En unik begränsning på T är uppfylld om och endast om det inte finns två rader R1 och R2 i T så att R1 och R2 har samma icke-NULL-värden i de unika kolumnerna."
Så en tabell T med en unik begränsning på kol1 tillåter flera rader med en NULL i kol1, men tillåter inte flera rader med samma icke-NULL-värde i kol1.
Det som är lite svårare att förklara är vad som händer enligt standarden med en sammansatt unik begränsning. Säg att du har en unik begränsning definierad på (col1, col2). Du kan ha flera rader med (NULL, NULL), men du kan inte ha flera rader med (3, NULL), precis som du inte kan ha flera rader med (1, 100). På samma sätt kan du inte ha flera rader med (NULL, 300). Poängen är att du inte får ha flera rader med samma icke-NULL-värden i de unika kolumnerna. När det gäller en främmande nyckel kan du ha valfritt antal rader i referenstabellen med NULLs i alla referenskolumner, oavsett vad som finns i den refererade tabellen. Sådana rader är inte relaterade till några rader i den refererade tabellen (valfritt förhållande). Men om du har något icke-NULL-värde i någon av referenskolumnerna, måste det finnas en rad i den refererade tabellen med samma icke-NULL-värden i de refererade kolumnerna.
Anta att du har en databas på en plattform som stöder den unika standardbegränsningen och att du måste migrera databasen till SQL Server. Du kan stöta på problem med upprätthållandet av unika begränsningar i SQL Server om de unika kolumnerna stöder NULL. Data som ansågs giltiga i källsystemet kan anses vara ogiltiga i SQL Server. I följande avsnitt ska jag utforska ett antal möjliga lösningar i SQL Server.
Lösning 1, med filtrerat index eller indexerad vy
En vanlig lösning i T-SQL för att upprätthålla standardfunktionaliteten för unika begränsningar när det bara finns en målkolumn involverad är att använda ett unikt filtrerat index som filtrerar endast de rader där målkolumnen inte är NULL. Följande kod tar bort den befintliga unika begränsningen från T3 och implementerar ett sådant index:
ÄNDRA TABELL dbo.T3 SLÄPP BEGRÄNSNING UNQ_T3; SKAPA UNIKT INKLUSTERAT INDEX idx_col1_notnull PÅ dbo.T3(col1) DÄR col1 INTE ÄR NULL;
Eftersom indexet endast filtrerar rader där col1 inte är NULL, tillämpas dess UNIQUE-egenskap endast på icke-NULL col1-värdena.
Kom ihåg att T3 redan har en rad med en NULL i kol1. För att testa den här lösningen, använd följande kod för att lägga till en andra rad med en NULL i col1:
INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 400);
Den här koden körs framgångsrikt.
Kom ihåg att T3 redan har en rad med värdet 1 i kol1. Kör följande kod för att försöka lägga till en andra rad med 1 i kol1:
INSERT INTO dbo.T3(col1, col2) VALUES(1, 500);
Som väntat misslyckas detta försök med följande fel:
Msg 2601, Level 14, State 1Kan inte infoga dubblettnyckelrad i objektet 'dbo.T3' med unikt index 'idx_col1_notnull'. Dubblettnyckelvärdet är (1).
Använd följande kod för att fråga T3:
VÄLJ * FRÅN dbo.T3;
Denna kod genererar följande utdata som visar två rader med en NULL i col1:
col1 col2----------- -----------1 1002 -1NULL -13 300NULL 400
Den här lösningen fungerar bra när du behöver framtvinga unikhet på endast en kolumn, och när du inte behöver upprätthålla referensintegritet med en främmande nyckel som pekar på den kolumnen.
Problemet med den främmande nyckeln är att SQL Server kräver en primärnyckel eller en unik begränsning eller ett unikt ofiltrerat index som definieras i den refererade kolumnen. Det fungerar inte när det bara finns ett unikt filtrerat index definierat i den refererade kolumnen. Låt oss försöka skapa en tabell med en främmande nyckel som refererar till T3.col1. Använd först följande kod för att skapa tabellen T3:
SLIP TABELL OM FINNS dbo.T3FK;GO CREATE TABLE dbo.T3FK( id INT NOT NULL IDENTITETSBEGRÄNSNING PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL);
Prova sedan att köra följande kod i ett försök att lägga till en främmande nyckel som pekar från T3FK.col1 till T3.col1:
ÄNDRA TABELL dbo.T3FK LÄGG TILL BEGRÄNSNING FK_T3_T3FK UTLÄNDSK KEY(col1) REFERENSER dbo.T3(col1);
Detta försök misslyckas med följande fel:
Msg 1776, Level 16, State 0Det finns inga primära eller kandidatnycklar i den refererade tabellen 'dbo.T3' som matchar referenskolumnlistan i den främmande nyckeln 'FK_T3_T3FK'.
Msg 1750, Level 16, State 1
Det gick inte att skapa begränsning eller index. Se tidigare fel.
Släpp nu det befintliga filtrerade indexet för rengöring:
DROP INDEX idx_col1_notnull PÅ dbo.T3;
Släpp inte tabellen T3FK, eftersom du kommer att använda den i senare exempel.
Det andra problemet med den filtrerade indexlösningen, förutsatt att du inte behöver en främmande nyckel, är att den inte fungerar när du behöver genomdriva standardfunktionaliteten för unika begränsningar på flera kolumner, till exempel på kombinationen (col1, col2) . Kom ihåg att den standardmässiga unika begränsningen inte tillåter dubbla icke-NULL-kombinationer av värden i de unika kolumnerna. För att implementera denna logik med ett filtrerat index behöver du bara filtrera rader där någon av de unika kolumnerna inte är NULL. Med andra ord behöver du bara filtrera rader som inte har NULL i alla unika kolumner. Tyvärr tillåter filtrerade index bara mycket enkla uttryck. De stöder inte ELLER, NOT eller manipulation på kolumnerna. Så ingen av följande indexdefinitioner stöds för närvarande:
SKAPA UNIKT INKLUSTERAT INDEX idx_customunique PÅ dbo.T3(col1, col2) DÄR col1 INTE ÄR NULL ELLER col2 INTE ÄR NULL; SKAPA UNIKT INKLUSTERAT INDEX idx_customunique PÅ dbo.T3(col1, col2) WHERE NOT (col1 ÄR NULL OCH col2 ÄR NULL); SKAPA UNIKT INKLUSTERAT INDEX idx_customunique PÅ dbo.T3(col1, col2) WHERE COALESCE(col1, col2) INTE ÄR NULL;
Lösningen i ett sådant fall är att skapa en indexerad vy baserat på en fråga som returnerar col1 och col2 från T3 med en av WHERE-satserna ovan, med ett unikt klustrat index på (col1, col2), så här:
SKAPA VY dbo.T3CustomUnique MED SCHEMABINDINGAS SELECT col1, col2 FRÅN dbo.T3 WHERE col1 IS NOT NULL OR col2 IS NOT NULL;GO SKAPA UNIKT CLUSTERED INDEX idx_col1_col2 PÅ dbo.T3pre> Col1, col2;Du kommer att tillåtas lägga till flera rader med (NULL, NULL) i (col1, col2), men du kommer inte att tillåtas lägga till flera förekomster av icke-NULL-kombinationer av värden i (col1, col2), som (3) , NULL) eller (NULL, 300) eller (1, 100). Ändå stöder den här lösningen inte en främmande nyckel.
Kör nu följande kod för rengöring:
SLÄPP VISNING OM FINNS dbo.T3CustomUnique;Lösning 2, med hjälp av surrogatnyckel och beräknad kolumn
Lösningarna med det filtrerade indexet och den indexerade vyn är bra så länge du inte behöver stödja en främmande nyckel. Men vad händer om du behöver upprätthålla referensintegritet? Ett alternativ är att fortsätta använda det filtrerade indexet eller den indexerade vylösningen för att framtvinga unikhet, och använda triggers för att upprätthålla referensintegritet. Det här alternativet är dock ganska dyrt.
Ett annat alternativ är att använda en helt annan lösning för den unika delen som stöder en främmande nyckel. Lösningen innebär att man lägger till två kolumner i den refererade tabellen (T3 i vårt fall). En kolumn som kallas id är en surrogatnyckel med en identitetsegenskap. En annan kolumn som kallas flagga är en beständig beräknad kolumn som returnerar id när col1 är NULL och 0 när det inte är NULL. Du upprätthåller sedan en unik begränsning på kombinationen av col1 och flagga. Här är koden för att lägga till de två kolumnerna och den unika begränsningen:
ÄNDRA TABELL dbo.T3 LÄGG TILL ID INT INTE NULL IDENTITET, flagga SOM FALL NÄR col1 ÄR NULL SEN ID ANNARS 0 END PERSISTED, CONSTRAINT UNQ_T3_col1_flag UNIQUE(col1, flag);Använd följande kod för att fråga T3:
VÄLJ * FRÅN dbo.T3;Denna kod genererar följande utdata:
col1 col2 id-flagga ------------ ---------- ---------- ---------- -1 100 1 02 -1 2 0NULL -1 3 33 300 4 0NULL 400 5 5När det gäller referenstabellen (T3FK i vårt fall), lägger du till en beräknad kolumn som kallas flagga som alltid är inställd på 0, och en främmande nyckel definierad på (col1, flagga) som pekar på T3:s unika kolumner (col1, flagga), som så :
ÄNDRA TABELL dbo.T3FK ADD-flagga SOM 0 PERSISTERAD, BEGRÄNSNING FK_T3_T3FK UTLÄNDSK NYCKEL(col1, flagga) REFERENSER dbo.T3(col1, flagga);Låt oss testa den här lösningen.
Försök att lägga till följande rader:
INSERT INTO dbo.T3FK(col1, col2, othercol) VÄRDEN (1, 100, 'A'), (2, -1, 'B'), (3, 300, 'C');Dessa rader läggs till framgångsrikt, som de ska, eftersom alla har motsvarande refererade rader.
Fråga tabellen T3FK:
VÄLJ * FRÅN dbo.T3FK;Du får följande utdata:
id col1 col2 othercol flagga------------- ---------- ---------- ---------- -----------1 1 100 A 02 2 -1 B 03 3 300 C 0Försök att lägga till en rad som inte har en motsvarande rad i den refererade tabellen:
INSERT INTO dbo.T3FK(col1, col2, othercol) VÄRDEN (4, 400, 'D');Försöket avvisas, som det borde vara, med följande fel:
Meddelande 547, nivå 16, tillstånd 0
INSERT-satsen stod i konflikt med FOREIGN KEY-begränsningen "FK_T3_T3FK". Konflikten inträffade i databasen "TSQLV5", tabellen "dbo.T3".Testa att lägga till en rad i T3FK med en NULL i kol1:
INSERT INTO dbo.T3FK(col1, col2, othercol) VÄRDEN (NULL, NULL, 'E');Denna rad anses inte vara relaterad till någon rad i T3FK (valfritt förhållande) och ska enligt standarden tillåtas oavsett om det finns en NULL i den refererade tabellen i kol1. T-SQL stöder detta scenario och raden har lagts till.
Fråga tabellen T3FK:
VÄLJ * FRÅN dbo.T3FK;Denna kod genererar följande utdata:
id col1 col2 othercol flagga------------- ---------- ---------- ---------- -----------1 1 100 A 02 2 -1 B 03 3 300 C 05 NULL NULL E 0Lösningen fungerar bra när du behöver genomdriva standardfunktionaliteten för unikhet på en enda kolumn. Men det har ett problem när du behöver framtvinga unikhet på flera kolumner. För att demonstrera problemet, släpp först tabellerna T3 och T3FK:
SLAPP TABELL OM FINNS dbo.T3FK, dbo.T3;Använd följande kod för att återskapa T3 med en sammansatt unik begränsning på (col1, col2, flag):
SKAPA TABELL dbo.T3( kol1 INT NULL, kol2 INT NULL, id INT INTE NULL IDENTITET, flagga SOM FALL NÄR kol1 ÄR NULL OCH kol2 ÄR NULL DÅ ID ANNARS 0 END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE(col1, col2, flagga ));Observera att flaggan är inställd på id när både col1 och col2 är NULLs och 0 annars.
Den unika begränsningen i sig fungerar bra.
Kör följande kod för att lägga till några rader till T3, inklusive flera förekomster av (NULL, NULL) i (col1, col2):
INSERT INTO dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);Dessa rader har lagts till som de ska.
Försök att lägga till två förekomster av (1, NULL) i (kol1, kol2):
INSERT INTO dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);Detta försök misslyckas som det borde med följande fel:
Msg 2627, Level 14, State 1
Brott mot UNIQUE KEY-restriktionen 'UNQ_T3'. Kan inte infoga dubblettnyckel i objektet 'dbo.T3'. Dubblettnyckelvärdet är (1,, 0). Försök att lägga till två förekomster av (NULL, 100) i (kol1, kol2):
INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 100),(NULL, 100);Detta försök misslyckas också som det borde med följande fel:
Msg 2627, Level 14, State 1
Brott mot UNIQUE KEY-restriktionen 'UNQ_T3'. Kan inte infoga dubblettnyckel i objektet 'dbo.T3'. Dubblettnyckelvärdet är (, 100, 0). Försök att lägga till följande två rader, där ingen överträdelse ska ske:
INSERT INTO dbo.T3(col1, col2) VALUES(3, NULL),(NULL, 300);Dessa rader har lagts till.
Fråga tabellen T3 vid denna tidpunkt:
VÄLJ * FRÅN dbo.T3;Du får följande utdata:
col1 col2 id-flagga ------------ ---------- ---------- ---------- -1 100 1 01 200 2 0NULL NULL 3 3NULL NULL 4 43 NULL 9 0NULL 300 10 0Så långt har det gått bra.
Kör sedan följande kod för att skapa tabellen T3FK med en sammansatt främmande nyckel som refererar till T3:s unika kolumner:
SKAPA TABELL dbo.T3FK( id INT INTE NULL IDENTITETSBEGRÄNSNING PK_T3FK PRIMÄRNYCKEL, kol1 INT NULL, kol2 INT NULL, annankol VARCHAR(10) INTE NULL, flagga SOM 0 PERSISTED, CONSTRAINT FK_T3_T3FKEI, kol. KEY(Flagga FÖR) ) REFERENSER dbo.T3(col1, col2, flag));Denna lösning tillåter naturligtvis att lägga till rader till T3FK med (NULL, NULL) i (col1, col2). Problemet är att det också tillåter att rader läggas till en NULL i antingen col1 eller col2, även när den andra kolumnen inte är NULL, och den refererade tabellen T3 inte har en sådan tangentkombination. Försök till exempel att lägga till följande rad i T3FK:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');Den här raden har lagts till trots att det inte finns någon relaterad rad i T3. Enligt standarden ska denna rad inte tillåtas.
Tillbaka till ritbordet...
Lösning 3, med hjälp av surrogatnyckel och beräknad kolumn
Problemet med den tidigare lösningen (lösning 2) uppstår när du behöver stödja en sammansatt främmande nyckel. Det tillåter rader i referenstabellen som har en NULL i listan en referenskolumn, även när det finns icke-NULL-värden i andra referenskolumner, och ingen relaterad rad i den refererade tabellen. För att åtgärda detta kan du använda en variant av den tidigare lösningen, som vi kommer att kalla för lösning 3.
Använd först följande kod för att ta bort de befintliga tabellerna:
SLAPP TABELL OM FINNS dbo.T3FK, dbo.T3;I den nya lösningen i den refererade tabellen (T3 i vårt fall) använder du fortfarande den identitetsbaserade id-surrogatnyckelkolumnen. Du använder också en beständig beräknad kolumn som heter unqpath. När alla unika kolumner (col1 och col2 i vårt exempel) är NULL, ställer du in unqpath till en teckensträngsrepresentation av id (inga separatorer ). När någon av de unika kolumnerna inte är NULL, ställer du in unqpath till en teckensträngsrepresentation av en separat lista med unika kolumnvärden med hjälp av CONCAT-funktionen. Denna funktion ersätter en NULL med en tom sträng. Det som är viktigt är att se till att använda en separator som normalt inte kan visas i själva datan. Till exempel, med heltal col1 och col2 värden har du bara siffror, så vilken separator som helst förutom en siffra skulle fungera. I mitt exempel använder jag en punkt (.). Du upprätthåller sedan en unik begränsning på unqpath. Du kommer aldrig att ha en konflikt mellan unqpath-värdet när alla unika kolumner är NULL (inställd på id) kontra när någon av de unika kolumnerna inte är NULL eftersom unqpath i det förra fallet inte innehåller en separator, och i det senare fallet gör det . Kom ihåg att du kommer att använda lösning 3 när du har ett sammansatt nyckelfall, och förmodligen föredrar lösning 2, vilket är enklare, när du har ett nyckelfall med en kolumn. Om du vill använda lösning 3 också med en enkelkolumnnyckel och inte lösning 2, se bara till att du lägger till separatorn när den unika kolumnen inte är NULL även om det bara finns ett värde inblandat. På så sätt kommer du inte att ha en konflikt när id i en rad där col1 är NULL är lika med col1 i en annan rad, eftersom den förra inte kommer att ha någon separator och den senare har.
Här är koden för att skapa T3 med ovannämnda tillägg:
SKAPA TABELL dbo.T3( kol1 INT NULL, kol2 INT NULL, id INT INTE NULL IDENTITET, unqpath SOM FALL NÄR kol1 ÄR NULL OCH kol2 ÄR NULL DÅ CAST(id SOM VARCHAR(10)) ANNARS CONCAT(CAST(col1) AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE(unqpath));Innan vi tar itu med en främmande nyckel och referenstabellen, låt oss testa den unika begränsningen. Kom ihåg att det är tänkt att inte tillåta dubbletter av kombinationer av icke-NULL-värden i de unika kolumnerna, men det är tänkt att tillåta flera förekomster av alla-NULL i de unika kolumnerna.
Kör följande kod för att lägga till några rader, inklusive två förekomster av (NULL, NULL) i (col1, col2):
INSERT INTO dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);Den här koden slutförs som den ska.
Försök att lägga till två förekomster av (1, NULL) i (kol1, kol2):
INSERT INTO dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);Denna kod misslyckas med följande fel som den borde:
Msg 2627, Level 14, State 1
Brott mot UNIQUE KEY-restriktionen 'UNQ_T3'. Kan inte infoga dubblettnyckel i objektet 'dbo.T3'. Dubblettnyckelvärdet är (1.).På samma sätt avvisas även följande försök:
INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 100),(NULL, 100);Du får följande felmeddelande:
Msg 2627, Level 14, State 1
Brott mot UNIQUE KEY-restriktionen 'UNQ_T3'. Kan inte infoga dubblettnyckel i objektet 'dbo.T3'. Dupliceringsnyckelns värde är (.100).Kör följande kod för att lägga till ytterligare ett par rader:
INSERT INTO dbo.T3(col1, col2) VALUES(3, NULL),(NULL, 300);Den här koden körs som den ska.
Vid det här laget, fråga T3:
VÄLJ * FRÅN dbo.T3;Du får följande utdata:
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 .300Observera unqpath-värdena och se till att du förstår logiken bakom deras konstruktion, och skillnaden mellan ett fall där alla unika kolumner är NULL (ingen separator) kontra när minst en inte är NULL (separator finns).
När det gäller referenstabellen, T3FK; du definierar också en beräknad kolumn som heter unqpath, men i fallet där alla refererande kolumner är NULL ställer du kolumnen till NULL—inte till id. När någon av referenskolumnerna inte är NULL, konstruerar du samma separerade värdelista som du gjorde i T3. Du definierar sedan en främmande nyckel på T3FK.unqpath som pekar på T3.unqpath, så här:
SKAPA TABELL dbo.T3FK( id INT INTE NULL IDENTITETSBEGRÄNSNING PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL, unqpath SOM FALL NÄR col1 ÄR NULL OCH CORN NULL ÄR NULL (CAST(col1 AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) END PERSISTED, CONSTRAINT FK_T3_T3FK UTLÄNDSK KEY(unqpath) REFERENSER dbo.T3(unqpath));Denna främmande nyckel kommer att avvisa rader i T3FK där någon av referenskolumnerna inte är NULL och det inte finns någon relaterad rad i den refererade tabellen T3, som följande försök visar:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');Den här koden genererar följande fel:
Meddelande 547, nivå 16, tillstånd 0
INSERT-satsen stod i konflikt med FOREIGN KEY-begränsningen "FK_T3_T3FK". Konflikten inträffade i databasen "TSQLV5", tabellen "dbo.T3", kolumnen "unqpath".Denna lösning kommer rader i T3FK där någon av referenskolumnerna inte är NULL så länge som en relaterad rad i T3 finns, samt rader med NULLs i alla referenskolumner, eftersom sådana rader anses vara orelaterade till några rader i T3. Följande kod lägger till sådana giltiga rader till T3FK:
INSERT INTO dbo.T3FK(col1, col2, othercol) VÄRDEN (1 , 100 , 'A'), (1 , 200 , 'B'), (3 , NULL, 'C'), (NULL, 300 , 'D'), (NULL, NULL, 'E'), (NULL, NULL, 'F');Den här koden slutförs framgångsrikt.
Kör följande kod för att fråga T3FK:
VÄLJ * FRÅN dbo.T3FK;Du får följande utdata:
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 NULLSå det krävdes lite kreativitet, men nu har du en lösning för den unika standardbegränsningen, inklusive stöd för främmande nyckel.
Slutsats
Man skulle kunna tro att en unik begränsning är en enkel funktion, men det kan bli lite knepigt när du behöver stödja NULLs i de unika kolumnerna. Det blir mer komplicerat när du behöver implementera standardfunktionaliteten för unika begränsningar i T-SQL, eftersom de två använder olika regler när det gäller hur de hanterar NULL. I den här artikeln förklarade jag skillnaden mellan de två och gav lösningar som fungerar i T-SQL. Du kan använda ett enkelt filtrerat index när du behöver framtvinga unikhet på endast en NULL-kolumn, och du behöver inte stödja en främmande nyckel som refererar till den kolumnen. Men om du antingen behöver stödja en främmande nyckel eller en sammansatt unik begränsning med standardfunktionaliteten, behöver du en mer komplex implementering med en surrogatnyckel och en beräknad kolumn.