Denne artikel er del 4 i en serie om NULL-kompleksiteter. I de tidligere artikler (del 1, del 2 og del 3) dækkede jeg betydningen af NULL som en markør for en manglende værdi, hvordan NULL'er opfører sig i sammenligninger og i andre forespørgselselementer og standard NULL-håndteringsfunktioner, der ikke er endnu tilgængelig i T-SQL. I denne måned dækker jeg forskellen mellem den måde, en unik begrænsning er defineret i ISO/IEC SQL-standarden, og den måde, den fungerer på i T-SQL. Jeg vil også levere skræddersyede løsninger, som du kan implementere, hvis du har brug for standardfunktionaliteten.
Standard UNIK begrænsning
SQL Server håndterer NULL-værdier ligesom ikke-NULL-værdier med det formål at håndhæve en unik begrænsning. Det vil sige, at en unik begrænsning på T er opfyldt, hvis og kun hvis der ikke eksisterer to rækker R1 og R2 i T, således at R1 og R2 har den samme kombination af NULL-værdier og ikke-NULL-værdier i de unikke kolonner. Antag for eksempel, at du definerer en unik begrænsning på col1, som er en NULLbar kolonne af en INT-datatype. Et forsøg på at ændre tabellen på en måde, der ville resultere i mere end én række med en NULL i col1, vil blive afvist, ligesom en ændring, der ville resultere i mere end én række med værdien 1 i col1, vil blive afvist.
Antag, at du definerer en sammensat unik begrænsning på kombinationen af NULLable INT-kolonner col1 og col2. Et forsøg på at ændre tabellen på en måde, der ville resultere i mere end én forekomst af en af følgende kombinationer af (col1, col2) værdier, vil blive afvist:(NULL, NULL), (3, NULL), (NULL, 300) ), (1, 100).
Så som du kan se, behandler T-SQL-implementeringen af den unikke begrænsning NULL'er ligesom ikke-NULL-værdier med det formål at håndhæve unikhed.
Hvis du vil definere en fremmednøgle på en tabel X, der refererer til en tabel Y, skal du gennemtvinge entydighed på de(n) refererede kolonne(r) med en af følgende muligheder:
- Primær nøgle
- Unik begrænsning
- Ikke-filtreret unikt indeks
En primær nøgle er ikke tilladt på NULL-kolonner. Både en unik begrænsning (som skaber et indeks under omslagene) og et eksplicit oprettet unikt indeks er tilladt på NULL-kolonner og håndhæver deres unikke karakter i T-SQL ved hjælp af den førnævnte logik. Referencetabellen må have rækker med NULL i referencekolonnen, uanset om den refererede tabel har en række med NULL i den refererede kolonne. Tanken er at understøtte et valgfrit forhold. Nogle rækker i referencetabellen kan være dem, der ikke er relateret til nogen rækker i den refererede tabel. Du implementerer dette ved at bruge en NULL i referencekolonnen.
For at demonstrere T-SQL-implementeringen af en unik begrænsning skal du køre følgende kode, som opretter en tabel kaldet T3 med en unik begrænsning defineret på NULLable INT-kolonnen col1 og udfylder den med et par eksempelrækker:
BRUG tempdb;GO DROP TABLE HVIS FINDER dbo.T3;GO OPRET TABEL 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);
Brug følgende kode til at forespørge i tabellen:
VÆLG * FRA dbo.T3;
Denne forespørgsel genererer følgende output:
col1 col2----------- -----------1 1002 -1NULL -13 300
Forsøg at indsætte en anden række med et NULL i col1:
INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 400);
Dette forsøg afvises, og du får følgende fejlmeddelelse:
Msg 2627, Level 14, State 1Overtrædelse af UNIQUE KEY constraint 'UNQ_T3'. Kan ikke indsætte dubletnøgle i objektet 'dbo.T3'. Dubletnøgleværdien er (
Den unikke standarddefinition af begrænsninger er en smule anderledes end T-SQL-versionen. Den største forskel har at gøre med NULL-håndteringen. Her er den unikke begrænsningsdefinition fra standarden:
"En unik begrænsning på T er opfyldt, hvis og kun hvis der ikke eksisterer to rækker R1 og R2 i T, således at R1 og R2 har de samme ikke-NULL-værdier i de unikke kolonner."
Så en tabel T med en unik begrænsning på col1 vil tillade flere rækker med en NULL i col1, men ikke tillade flere rækker med den samme ikke-NULL værdi i col1.
Det, der er lidt sværere at forklare, er, hvad der sker i henhold til standarden med en sammensat unik begrænsning. Sig, at du har en unik begrænsning defineret på (col1, col2). Du kan have flere rækker med (NULL, NULL), men du kan ikke have flere rækker med (3, NULL), ligesom du ikke kan have flere rækker med (1, 100). På samme måde kan du ikke have flere rækker med (NULL, 300). Pointen er, at du ikke må have flere rækker med de samme ikke-NULL-værdier i de unikke kolonner. Hvad angår en fremmednøgle, kan du have et hvilket som helst antal rækker i referencetabellen med NULL'er i alle referencekolonner, uanset hvad der findes i den refererede tabel. Sådanne rækker er ikke relateret til nogen rækker i den refererede tabel (valgfrit forhold). Men hvis du har en ikke-NULL-værdi i nogen af de refererende kolonner, skal der findes en række i den refererede tabel med de samme ikke-NULL-værdier i de refererede kolonner.
Antag, at du har en database på en platform, der understøtter den unikke standardbegrænsning, og du skal migrere databasen til SQL Server. Du kan få problemer med håndhævelsen af unikke begrænsninger i SQL Server, hvis de unikke kolonner understøtter NULL'er. Data, der blev betragtet som gyldige i kildesystemet, kan blive betragtet som ugyldige i SQL Server. I de følgende afsnit vil jeg udforske en række mulige løsninger i SQL Server.
Løsning 1, ved hjælp af filtreret indeks eller indekseret visning
En almindelig løsning i T-SQL til at håndhæve den standard unikke begrænsningsfunktionalitet, når der kun er én målkolonne involveret, er at bruge et unikt filtreret indeks, der kun filtrerer de rækker, hvor målkolonnen ikke er NULL. Følgende kode fjerner den eksisterende unikke begrænsning fra T3 og implementerer et sådant indeks:
ÆNDRINGSTABEL dbo.T3 DROP BEGRÆNSNING UNQ_T3; OPRET UNIKT IKKE-KLYNGERET INDEKS idx_col1_notnull PÅ dbo.T3(col1) HVOR col1 IKKE ER NULL;
Da indekset kun filtrerer rækker, hvor col1 ikke er NULL, håndhæves dens UNIQUE-egenskab kun på de ikke-NULL col1-værdier.
Husk at T3 allerede har en række med en NULL i col1. For at teste denne løsning skal du bruge følgende kode til at tilføje en anden række med en NULL i col1:
INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 400);
Denne kode kører med succes.
Husk at T3 allerede har en række med værdien 1 i col1. Kør følgende kode for at forsøge at tilføje en anden række med 1 i col1:
INSERT INTO dbo.T3(col1, col2) VALUES(1, 500);
Som forventet mislykkes dette forsøg med følgende fejl:
Meddelelse 2601, niveau 14, tilstand 1Kan ikke indsætte dublet nøglerække i objektet 'dbo.T3' med det unikke indeks 'idx_col1_notnull'. Dubletnøgleværdien er (1).
Brug følgende kode til at forespørge T3:
VÆLG * FRA dbo.T3;
Denne kode genererer følgende output, der viser to rækker med en NULL i col1:
col1 col2----------- -----------1 1002 -1NULL -13 300NULL 400
Denne løsning fungerer godt, når du kun skal håndhæve unikhed på én kolonne, og når du ikke behøver at håndhæve referenceintegritet med en fremmednøgle, der peger på den kolonne.
Problemet med den fremmede nøgle er, at SQL Server kræver en primær nøgle eller en unik begrænsning eller et unikt ikke-filtreret indeks defineret i den refererede kolonne. Det virker ikke, når der kun er et unikt filtreret indeks defineret på den refererede kolonne. Lad os prøve at oprette en tabel med en fremmednøgle, der refererer til T3.col1. Brug først følgende kode til at oprette tabellen T3:
DROP TABLE IF EXISTS 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);
Prøv derefter at køre følgende kode i et forsøg på at tilføje en fremmednøgle, der peger fra T3FK.col1 til T3.col1:
ÆNDRINGSTABEL dbo.T3FK TILFØJ BEGRÆNSNING FK_T3_T3FK UDENLANDSKE NØGLE(col1) REFERENCER dbo.T3(col1);
Dette forsøg mislykkes med følgende fejl:
Msg 1776, Level 16, State 0Der er ingen primære eller kandidatnøgler i den refererede tabel 'dbo.T3', der matcher referencekolonnelisten i fremmednøglen 'FK_T3_T3FK'.
Msg 1750, Level 16, State 1
Kunne ikke oprette begrænsning eller indeks. Se tidligere fejl.
På dette tidspunkt skal du slippe det eksisterende filtrerede indeks for oprydning:
DROP INDEX idx_col1_notnull PÅ dbo.T3;
Slip ikke tabellen T3FK, da du vil bruge den i senere eksempler.
Det andet problem med den filtrerede indeksløsning, forudsat at du ikke har brug for en fremmednøgle, er, at den ikke virker, når du skal håndhæve standarden unikke begrænsningsfunktionalitet på flere kolonner, for eksempel på kombinationen (col1, col2) . Husk, at den unikke standardbegrænsning ikke tillader duplikerede ikke-NULL-kombinationer af værdier i de unikke kolonner. For at implementere denne logik med et filtreret indeks skal du kun filtrere rækker, hvor nogen af de unikke kolonner ikke er NULL. Udtrykt anderledes skal du kun filtrere rækker, der ikke har NULL i alle unikke kolonner. Desværre tillader filtrerede indekser kun meget simple udtryk. De understøtter ikke OR, NOT eller manipulation på kolonnerne. Så ingen af følgende indeksdefinitioner understøttes i øjeblikket:
OPRET UNIKT IKKE-KLUSTERET INDEKS idx_customunique PÅ dbo.T3(col1, col2) HVOR col1 IKKE ER NULL ELLER col2 IKKE ER NULL; OPRET UNIKT IKKE-KLYNGERET INDEKS idx_customunique PÅ dbo.T3(col1, col2) WHERE NOT (col1 ER NULL, OG col2 ER NULL); OPRET UNIKT IKKE-KLUNGERET INDEKS idx_customunique PÅ dbo.T3(col1, col2) HVOR COALESCE(col1, col2) IKKE ER NULL;
Løsningen i et sådant tilfælde er at oprette en indekseret visning baseret på en forespørgsel, der returnerer col1 og col2 fra T3 med et af WHERE-sætningerne ovenfor, med et unikt klynget indeks på (col1, col2), som sådan:
OPRET VISNING dbo.T3CustomUnique MED SCHEMABINDINGAS SELECT col1, col2 FROM dbo.T3 WHERE col1 IS NOT NULL ELLER col2 IS NOT NULL;GO OPRET UNIKT CLUSTERED INDEX idx_col1_col2 PÅ dbo.T3pre>CustomUnique);(Du får lov til at tilføje flere rækker med (NULL, NULL) i (col1, col2), men du får ikke lov til at tilføje flere forekomster af ikke-NULL kombinationer af værdier i (col1, col2), såsom (3) , NULL) eller (NULL, 300) eller (1, 100). Alligevel understøtter denne løsning ikke en fremmednøgle.
På dette tidspunkt skal du køre følgende kode til oprydning:
DROP VISNING HVIS FINDER dbo.T3CustomUnique;Løsning 2, ved hjælp af surrogatnøgle og beregnet kolonne
Løsningerne med det filtrerede indeks og den indekserede visning er gode, så længe du ikke behøver at understøtte en fremmednøgle. Men hvad hvis du har brug for at håndhæve referentiel integritet? En mulighed er at blive ved med at bruge det filtrerede indeks eller den indekserede visningsløsning for at håndhæve unikhed og bruge triggere til at håndhæve referenceintegritet. Denne mulighed er dog ret dyr.
En anden mulighed er at bruge en helt anden løsning til den unikke del, der understøtter en fremmednøgle. Løsningen involverer at tilføje to kolonner til den refererede tabel (T3 i vores tilfælde). En kolonne kaldet id er en surrogatnøgle med en identitetsegenskab. En anden kolonne kaldet flag er en vedvarende beregnet kolonne, der returnerer id, når col1 er NULL og 0, når den ikke er NULL. Du håndhæver derefter en unik begrænsning på kombinationen af col1 og flag. Her er koden til at tilføje de to kolonner og den unikke begrænsning:
ÆNDRING TABEL dbo.T3 TILFØJ id INT IKKE NULL IDENTITET, flag SOM CASE, NÅR col1 ER NULL, SÅ ID ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3_col1_flag UNIQUE(col1, flag);Brug følgende kode til at forespørge T3:
VÆLG * FRA dbo.T3;Denne kode genererer følgende output:
col1 col2 id flag ------------ ---------- ---------- ---------- -1 100 1 02 -1 2 0NULL -1 3 33 300 4 0NULL 400 5 5Hvad angår referencetabellen (T3FK i vores tilfælde), tilføjer du en beregnet kolonne kaldet flag, der altid er sat til 0, og en fremmednøgle defineret på (col1, flag), der peger på T3's unikke kolonner (col1, flag), som f.eks. :
ÆNDR TABEL dbo.T3FK TILFØJ flag SOM 0 VEDVARENDE, BEGRÆNSNING FK_T3_T3FK UDENLANDSKE NØGLE(col1, flag) REFERENCER dbo.T3(col1, flag);Lad os teste denne løsning.
Prøv at tilføje følgende rækker:
INSERT INTO dbo.T3FK(col1, col2, othercol) VÆRDIER (1, 100, 'A'), (2, -1, 'B'), (3, 300, 'C');>Disse rækker tilføjes med succes, som de skal, da alle har tilsvarende referencer.
Spørg tabellen T3FK:
VÆLG * FRA dbo.T3FK;Du får følgende output:
id col1 col2 othercol flag------------ ----------- ------------------ ---------- -----------1 1 100 A 02 2 -1 B 03 3 300 C 0Prøv at tilføje en række, der ikke har en tilsvarende række i den refererede tabel:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES (4, 400, 'D');Forsøget afvises, som det burde være, med følgende fejl:
Msg 547, Level 16, State 0
INSERT-sætningen var i konflikt med FOREIGN KEY-begrænsningen "FK_T3_T3FK". Konflikten opstod i databasen "TSQLV5", tabel "dbo.T3".Prøv at tilføje en række til T3FK med en NULL i col1:
INSERT INTO dbo.T3FK(col1, col2, othercol) VÆRDIER (NULL, NULL, 'E');Denne række anses for ikke at være relateret til nogen række i T3FK (valgfrit forhold) og bør ifølge standarden tillades, uanset om der findes en NULL i den refererede tabel i col1. T-SQL understøtter dette scenario, og rækken er tilføjet med succes.
Spørg tabellen T3FK:
VÆLG * FRA dbo.T3FK;Denne kode genererer følgende output:
id col1 col2 othercol flag------------ ----------- ------------------ ---------- -----------1 1 100 A 02 2 -1 B 03 3 300 C 05 NULL NULL E 0Løsningen fungerer godt, når du har brug for at håndhæve standard unikke funktionaliteten på en enkelt kolonne. Men det har et problem, når du skal håndhæve unikhed på flere kolonner. For at demonstrere problemet skal du først slippe tabellerne T3 og T3FK:
DROP TABEL HVIS FINDER dbo.T3FK, dbo.T3;Brug følgende kode til at genskabe T3 med en sammensat unik begrænsning på (col1, col2, flag):
CREATE TABLE dbo.T3( col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, flag SOM CASE, NÅR col1 ER NULL, og col2 ER NULL, SÅ ID ANDERS 0 END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE(col1, col2, flag ));Bemærk, at flaget er sat til id, når både col1 og col2 er NULL og ellers 0.
Selve den unikke begrænsning fungerer godt.
Kør følgende kode for at tilføje et par rækker til T3, inklusive flere forekomster af (NULL, NULL) i (col1, col2):
INSERT INTO dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);Disse rækker tilføjes som de skal.
Prøv at tilføje to forekomster af (1, NULL) i (col1, col2):
INSERT INTO dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);Dette forsøg mislykkes som det burde med følgende fejl:
Msg 2627, Level 14, State 1
Overtrædelse af UNIQUE KEY constraint 'UNQ_T3'. Kan ikke indsætte dubletnøgle i objektet 'dbo.T3'. Dubletnøgleværdien er (1,, 0). Prøv at tilføje to forekomster af (NULL, 100) i (col1, col2):
INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 100),(NULL, 100);Dette forsøg mislykkes også som det burde med følgende fejl:
Msg 2627, Level 14, State 1
Overtrædelse af UNIQUE KEY constraint 'UNQ_T3'. Kan ikke indsætte dubletnøgle i objektet 'dbo.T3'. Dubletnøgleværdien er (, 100, 0). Prøv at tilføje følgende to rækker, hvor der ikke bør ske nogen overtrædelse:
INSERT INTO dbo.T3(col1, col2) VALUES(3, NULL),(NULL, 300);Disse rækker er tilføjet.
Spørg tabellen T3 på dette tidspunkt:
VÆLG * FRA dbo.T3;Du får følgende output:
col1 col2 id flag ------------ ---------- ---------- ---------- -1 100 1 01 200 2 0NULL NULL 3 3NULL NULL 4 43 NULL 9 0NULL 300 10 0Så langt så godt.
Kør derefter følgende kode for at oprette tabellen T3FK med en sammensat fremmednøgle, der refererer til T3s unikke kolonner:
CREATE TABLE dbo.T3FK( id INT IKKE NULL IDENTITETSBEGRÆNSNING PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL, flag AS 0 PERSISTED, CONSTRAINT FK_T3_T3FKEI, kol. KEY, flag FOR ) REFERENCER dbo.T3(col1, col2, flag));Denne løsning giver naturligvis mulighed for at tilføje rækker til T3FK med (NULL, NULL) i (col1, col2). Problemet er, at det også tillader at tilføje rækker en NULL i enten col1 eller col2, selv når den anden kolonne ikke er NULL, og den refererede tabel T3 ikke har en sådan tastekombination. Prøv f.eks. at tilføje følgende række til T3FK:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');Denne række er tilføjet med succes, selvom der ikke er nogen relateret række i T3. Ifølge standarden bør denne række ikke tillades.
Tilbage til tegnebrættet...
Løsning 3, ved hjælp af surrogatnøgle og beregnet kolonne
Problemet med den tidligere løsning (løsning 2) opstår, når du skal understøtte en sammensat fremmednøgle. Det tillader rækker i referencetabellen, der har en NULL på liste én referencekolonne, selv når der er ikke-NULL værdier i andre referencekolonner, og ingen relateret række i den refererede tabel. For at løse dette kan du bruge en variant af den tidligere løsning, som vi vil kalde løsning 3.
Brug først følgende kode til at slette de eksisterende tabeller:
DROP TABEL HVIS FINDER dbo.T3FK, dbo.T3;I den nye løsning i den refererede tabel (T3 i vores tilfælde) bruger du stadig den identitetsbaserede id surrogatnøglekolonne. Du bruger også en vedvarende beregnet kolonne kaldet unqpath. Når alle de unikke kolonner (col1 og col2 i vores eksempel) er NULL, indstiller du unqpath til en tegnstrengrepræsentation af id (ingen separatorer ). Når nogen af de unikke kolonner ikke er NULL, sætter du unqpath til en tegnstrengsrepræsentation af en adskilt liste over de unikke kolonneværdier ved hjælp af CONCAT-funktionen. Denne funktion erstatter en NULL med en tom streng. Det, der er vigtigt, er at sørge for at bruge en separator, der normalt ikke kan vises i selve dataene. For eksempel, med heltal col1 og col2 værdier har du kun cifre, så enhver separator bortset fra et ciffer ville fungere. I mit eksempel vil jeg bruge en prik (.). Du håndhæver derefter en unik begrænsning på unqpath. Du vil aldrig have en konflikt mellem unqpath-værdien, når alle unikke kolonner er NULL (sat til id) versus når nogen af de unikke kolonner ikke er NULL, fordi i førstnævnte tilfælde indeholder unqpath ikke en separator, og i sidstnævnte tilfælde gør det. . Husk, at du vil bruge Løsning 3, når du har et sammensat nøglehus, og sandsynligvis foretrækker Løsning 2, som er enklere, når du har en enkelt kolonnenøglesag. Hvis du også vil bruge løsning 3 med en enkelt-kolonne nøgle og ikke løsning 2, skal du bare sørge for at tilføje separatoren, når den unikke kolonne ikke er NULL, selvom der kun er én værdi involveret. På denne måde vil du ikke have en konflikt, når id i en række, hvor col1 er NULL, er lig med col1 i en anden række, da førstnævnte ikke har nogen separator, og sidstnævnte vil.
Her er koden til at oprette T3 med de førnævnte tilføjelser:
CREATE TABLE dbo.T3( col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, unqpath AS CASE, NÅR col1 ER NULL, OG col2 ER NULL, SÅ CAST(id SOM VARCHAR(10)) ELLES CONCAT(CAST(col1) AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE(unqpath));Før vi beskæftiger os med en fremmednøgle og referencetabellen, lad os teste den unikke begrænsning. Husk, det er meningen, at det ikke skal tillade duplikerede kombinationer af ikke-NULL-værdier i de unikke kolonner, men det er meningen, at det skal tillade flere forekomster af alle-NULL-værdier i de unikke kolonner.
Kør følgende kode for at tilføje et par rækker, inklusive to forekomster af (NULL, NULL) i (col1, col2):
INSERT INTO dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);Denne kode fuldføres som den skal.
Prøv at tilføje to forekomster af (1, NULL) i (col1, col2):
INSERT INTO dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);Denne kode fejler med følgende fejl, som den burde:
Msg 2627, Level 14, State 1
Overtrædelse af UNIQUE KEY constraint 'UNQ_T3'. Kan ikke indsætte dubletnøgle i objektet 'dbo.T3'. Dubletnøgleværdien er (1.).På samme måde afvises følgende forsøg også:
INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 100),(NULL, 100);Du får følgende fejlmeddelelse:
Msg 2627, Level 14, State 1
Overtrædelse af UNIQUE KEY constraint 'UNQ_T3'. Kan ikke indsætte dubletnøgle i objektet 'dbo.T3'. Dubletnøgleværdien er (.100).Kør følgende kode for at tilføje et par rækker mere:
INSERT INTO dbo.T3(col1, col2) VALUES(3, NULL),(NULL, 300);Denne kode kører med succes, som den skal.
På dette tidspunkt skal du forespørge T3:
VÆLG * FRA dbo.T3;Du får følgende output:
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 .300Observer unqpath-værdierne, og sørg for, at du forstår logikken bag deres konstruktion, og forskellen mellem et tilfælde, hvor alle unikke kolonner er NULL (ingen separator) versus når mindst én ikke er NULL (separator findes).
Hvad angår referencetabellen, T3FK; du definerer også en beregnet kolonne kaldet unqpath, men i det tilfælde, hvor alle refererende kolonner er NULL, sætter du kolonnen til NULL - ikke til id. Når nogen af de refererende kolonner ikke er NULL, konstruerer du den samme adskilte liste over værdier, som du gjorde i T3. Du definerer derefter en fremmednøgle på T3FK.unqpath, der peger på T3.unqpath, sådan:
CREATE TABLE dbo.T3FK( id INT IKKE NULL IDENTITETSBEGRÆNSNING PK_T3FK PRIMARY KEY, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL, unqpath SOM TILFÆLDE, NÅR col1 ER NULL, OG CONNELSEN NULL ER NULL (CAST(col1 AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) SLUT PERSISTED, CONSTRAINT FK_T3_T3FK UDENLANDSKE KEY(unqpath) REFERENCER dbo.T3(unqpath));Denne fremmednøgle vil afvise rækker i T3FK, hvor nogen af de refererende kolonner ikke er NULL, og der ikke er nogen relateret række i den refererede tabel T3, som følgende forsøg viser:
INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');Denne kode genererer følgende fejl:
Msg 547, Level 16, State 0
INSERT-sætningen var i konflikt med FOREIGN KEY-begrænsningen "FK_T3_T3FK". Konflikten opstod i databasen "TSQLV5", tabel "dbo.T3", kolonnen "unqpath".Denne løsning vil rækker i T3FK, hvor nogen af referencekolonnerne ikke er NULL, så længe der eksisterer en relateret række i T3, samt rækker med NULL i alle referencekolonner, da sådanne rækker anses for at være urelaterede til nogen rækker i T3. Følgende kode tilføjer sådanne gyldige rækker til T3FK:
INSERT INTO dbo.T3FK(col1, col2, othercol) VÆRDIER (1 , 100 , 'A'), (1 , 200 , 'B'), (3 , NULL, 'C'), (NULL, 300 , 'D'), (NULL, NULL, 'E'), (NULL, NULL, 'F');Denne kode er fuldført.
Kør følgende kode for at forespørge T3FK:
VÆLG * FRA dbo.T3FK;Du får følgende output:
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ævede en lille smule kreativitet, men nu har du en løsning til den standard, unikke begrænsning, inklusive understøttelse af udenlandsk nøgle.
Konklusion
Du skulle tro, at en unik begrænsning er en ligetil funktion, men det kan blive en smule vanskeligt, når du skal understøtte NULL'er i de unikke kolonner. Det bliver mere komplekst, når du skal implementere den standard unikke begrænsningsfunktionalitet i T-SQL, da de to bruger forskellige regler i forhold til, hvordan de håndterer NULL'er. I denne artikel forklarede jeg forskellen mellem de to og gav løsninger, der virker i T-SQL. Du kan bruge et simpelt filtreret indeks, når du kun skal gennemtvinge entydighed på én NULL-bar kolonne, og du behøver ikke at understøtte en fremmednøgle, der refererer til den kolonne. Men hvis du enten skal understøtte en fremmednøgle eller en sammensat unik begrænsning med standardfunktionaliteten, har du brug for en mere kompleks implementering med en surrogatnøgle og en beregnet kolonne.