sql >> Databáze >  >> RDS >> Database

Složitost NULL – Část 4, Chybějící standardní jedinečné omezení

Tento článek je částí 4 v sérii o složitosti NULL. V předchozích článcích (část 1, část 2 a část 3) jsem se zabýval významem NULL jako značky pro chybějící hodnotu, jak se hodnoty NULL chovají při porovnávání a v jiných prvcích dotazu a standardními funkcemi zpracování NULL, které nejsou zatím dostupný v T-SQL. Tento měsíc se budu věnovat rozdílu mezi tím, jak je jedinečné omezení definováno ve standardu ISO/IEC SQL, a tím, jak funguje v T-SQL. Poskytnu také přizpůsobená řešení, která můžete implementovat, pokud potřebujete standardní funkce.

Standardní UNIKÁTNÍ omezení

SQL Server zpracovává hodnoty NULL stejně jako hodnoty bez hodnoty NULL za účelem vynucení jedinečného omezení. To znamená, že jedinečné omezení na T je splněno tehdy a pouze tehdy, když neexistují dva řádky R1 a R2 z T, takže R1 a R2 mají stejnou kombinaci hodnot NULL a hodnot bez NULL v jedinečných sloupcích. Předpokládejme například, že definujete jedinečné omezení pro col1, což je sloupec s hodnotou NULL datového typu INT. Pokus o úpravu tabulky způsobem, který by vedl k více než jednomu řádku s NULL v col1, bude zamítnut, stejně jako bude zamítnuta úprava, která by vedla k více než jednomu řádku s hodnotou 1 v col1.

Předpokládejme, že definujete složené jedinečné omezení pro kombinaci sloupců INT s hodnotou NULL col1 a col2. Pokus o úpravu tabulky způsobem, který by vedl k více než jednomu výskytu kterékoli z následujících kombinací hodnot (col1, col2), bude zamítnut:(NULL, NULL), (3, NULL), (NULL, 300 ), (1, 100).

Jak tedy vidíte, implementace jedinečného omezení v T-SQL zachází s hodnotami NULL stejně jako s hodnotami bez hodnoty NULL za účelem vynucení jedinečnosti.

Pokud chcete definovat cizí klíč v nějaké tabulce X odkazující na nějakou tabulku Y, musíte vynutit jedinečnost odkazovaného sloupce (sloupců) jednou z následujících možností:

  • Primární klíč
  • Jedinečné omezení
  • Nefiltrovaný jedinečný index

Primární klíč není povolen ve sloupcích s hodnotou NULL. Jak jedinečné omezení (které vytváří index pod kryty), tak explicitně vytvořený jedinečný index jsou povoleny na sloupcích s hodnotou NULL a vynucují jejich jedinečnost v T-SQL pomocí výše uvedené logiky. Odkazující tabulka může mít v odkazujícím sloupci řádky s NULL, bez ohledu na to, zda má odkazovaná tabulka v odkazovaném sloupci řádek s NULL. Cílem je podpořit volitelný vztah. Některé řádky v odkazující tabulce mohou být ty, které nesouvisí s žádnými řádky v odkazované tabulce. Implementujete to pomocí NULL ve sloupci odkazu.

Chcete-li předvést implementaci jedinečného omezení T-SQL, spusťte následující kód, který vytvoří tabulku nazvanou T3 s jedinečným omezením definovaným ve sloupci NULLable INT col1 a naplní ji několika ukázkovými řádky:

POUŽÍVEJTE tempdb;PŘEJĎTE TABULU ZAPNOUT, POKUD EXISTUJE dbo.T3;GO VYTVOŘIT TABULKU 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);

K dotazu na tabulku použijte následující kód:

SELECT * FROM dbo.T3;

Tento dotaz generuje následující výstup:

col1 col2----------- -----------1 1002 -1NULL -13 300

Pokuste se vložit druhý řádek s NULL do col1:

INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 400);

Tento pokus byl zamítnut a zobrazí se následující chyba:

Msg 2627, Level 14, State 1
Porušení omezení UNIQUE KEY 'UNQ_T3'. Nelze vložit duplicitní klíč do objektu 'dbo.T3'. Hodnota duplicitního klíče je ().

Standardní definice jedinečného omezení je trochu jiná než verze T-SQL. Hlavní rozdíl souvisí se zpracováním NULL. Zde je jedinečná definice omezení ze standardu:

"Jedinečné omezení na T je splněno tehdy a pouze tehdy, když neexistují dva řádky R1 a R2 z T, takže R1 a R2 mají v jedinečných sloupcích stejné hodnoty než NULL."

Takže tabulka T s jedinečným omezením na col1 umožní více řádků s NULL v col1, ale zakáže více řádků se stejnou hodnotou než NULL v col1.

Trochu složitější je vysvětlit, co se stane podle standardu se složeným jedinečným omezením. Řekněme, že máte definováno jedinečné omezení (col1, col2). Můžete mít více řádků s (NULL, NULL), ale nemůžete mít více řádků s (3, NULL), stejně jako nemůžete mít více řádků s (1, 100). Podobně nemůžete mít více řádků s (NULL, 300). Jde o to, že v jedinečných sloupcích nemůžete mít více řádků se stejnými hodnotami než NULL. Pokud jde o cizí klíč, můžete mít v odkazovací tabulce libovolný počet řádků s hodnotami NULL ve všech odkazujících sloupcích, bez ohledu na to, co existuje v odkazované tabulce. Takové řádky nesouvisí s žádnými řádky v odkazované tabulce (volitelný vztah). Pokud však v některém z odkazujících sloupců máte hodnotu jinou než NULL, musí v odkazované tabulce existovat řádek se stejnými hodnotami než NULL v odkazovaných sloupcích.

Předpokládejme, že máte databázi na platformě, která podporuje standardní jedinečné omezení, a potřebujete tuto databázi migrovat na SQL Server. Pokud jedinečné sloupce podporují hodnoty NULL, můžete čelit problémům s vynucováním jedinečných omezení na serveru SQL Server. Data, která byla považována za platná ve zdrojovém systému, mohou být na serveru SQL považována za neplatná. V následujících částech prozkoumám řadu možných řešení v SQL Server.

Řešení 1, pomocí filtrovaného indexu nebo indexovaného zobrazení

Běžným řešením v T-SQL pro vynucení standardní funkce jedinečného omezení, když je zapojen pouze jeden cílový sloupec, je použití jedinečného filtrovaného indexu, který filtruje pouze řádky, kde cílový sloupec není NULL. Následující kód zruší stávající jedinečné omezení z T3 a implementuje takový index:

ALTER TABLE dbo.T3 DROP CONSTRAINT UNQ_T3; VYTVOŘTE UNIKÁTNÍ NEZAHRNUTÝ INDEX idx_col1_notnull NA dbo.T3(col1) KDE col1 NENÍ NULL;

Vzhledem k tomu, že index filtruje pouze řádky, kde sloupec1 není NULL, jeho vlastnost UNIQUE je vynucena pouze u hodnot sloupce1 bez hodnoty NULL.

Připomeňme, že T3 již má řádek s NULL v col1. Chcete-li toto řešení otestovat, použijte následující kód k přidání druhého řádku s hodnotou NULL v col1:

INSERT INTO dbo.T3(col1, col2) VALUES(NULL, 400);

Tento kód se úspěšně spustí.

Připomeňme, že T3 již má řádek s hodnotou 1 v col1. Spusťte následující kód a pokuste se přidat druhý řádek s 1 v col1:

INSERT INTO dbo.T3(col1, col2) VALUES(1, 500);

Podle očekávání se tento pokus nezdaří s následující chybou:

Msg 2601, Level 14, State 1
Nelze vložit duplicitní řádek klíče do objektu 'dbo.T3' s jedinečným indexem 'idx_col1_notnull'. Hodnota duplicitního klíče je (1).

K dotazu T3 použijte následující kód:

SELECT * FROM dbo.T3;

Tento kód generuje následující výstup zobrazující dva řádky s NULL v col1:

col1 col2----------- -----------1 1002 -1NULL -13 300NULL 400

Toto řešení funguje dobře, když potřebujete vynutit jedinečnost pouze u jednoho sloupce a když nepotřebujete vynutit referenční integritu s cizím klíčem ukazujícím na tento sloupec.

Problém s cizím klíčem je, že SQL Server vyžaduje primární klíč nebo jedinečné omezení nebo jedinečný nefiltrovaný index definovaný v odkazovaném sloupci. Nefunguje to, když je v odkazovaném sloupci definován pouze jedinečný filtrovaný index. Zkusme vytvořit tabulku s cizím klíčem odkazujícím na T3.col1. Nejprve použijte následující kód k vytvoření tabulky T3:

PUSTÍ TABULKU, POKUD EXISTUJE dbo.T3FK;GO VYTVOŘIT TABULKU dbo.T3FK( id INT NOT NULL OMEZENÍ IDENTITY PK_T3FK PRIMÁRNÍ KLÍČ, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL); 

Poté zkuste spustit následující kód ve snaze přidat cizí klíč ukazující z T3FK.col1 na T3.col1:

ALTER TABLE dbo.T3FK ADD CONSTRAINT FK_T3_T3FK CIZÍ KLÍČ(col1) REFERENCE dbo.T3(col1);

Tento pokus se nezdaří s následující chybou:

Msg 1776, Level 16, State 0
V odkazované tabulce 'dbo.T3' nejsou žádné primární nebo kandidátské klíče, které by odpovídaly seznamu referenčních sloupců v cizím klíči 'FK_T3_T3FK'.

Msg 1750, Level 16, State 1
Nelze vytvořit omezení nebo index. Viz předchozí chyby.

V tomto okamžiku zrušte existující filtrovaný index pro vyčištění:

DROP INDEX idx_col1_notnull ON dbo.T3;

Nepouštějte tabulku T3FK, protože ji použijete v pozdějších příkladech.

Dalším problémem s řešením filtrovaného indexu, za předpokladu, že nepotřebujete cizí klíč, je to, že nefunguje, když potřebujete vynutit funkci standardního jedinečného omezení na více sloupcích, například na kombinaci (col1, col2) . Pamatujte, že standardní jedinečné omezení zakazuje duplicitní kombinace hodnot v jedinečných sloupcích, které nejsou NULL. Chcete-li implementovat tuto logiku s filtrovaným indexem, musíte filtrovat pouze řádky, kde žádný z jedinečných sloupců není NULL. Jinak řečeno, musíte filtrovat pouze řádky, které ve všech jedinečných sloupcích nemají NULL. Bohužel filtrované indexy umožňují pouze velmi jednoduché výrazy. Nepodporují OR, NOT ani manipulaci se sloupci. Aktuálně tedy není podporována žádná z následujících definic indexu:

VYTVOŘENÍ UNIKÁTNÍHO NEZAHRNUTÉHO INDEXU idx_customunique NA dbo.T3(sloupec1, sloupec2) KDE sloupec1 NENÍ NULL NEBO sloupec2 NENÍ NULOVÝ; VYTVOŘIT JEDINEČNÝ NEZAHRNUTÝ INDEX idx_customunique NA dbo.T3(sloupec1, sloupec2) KDE NE (sloupec1 je NULL a sloupec2 JE NULL); VYTVOŘTE UNIKÁTNÍ NEZAHRNUTÝ INDEX idx_customunique NA dbo.T3(sloupec1, sloupec2) KDE COALESCE(sloupec1, sloupec2) NENÍ NULL;

Řešením v takovém případě je vytvořit indexované zobrazení založené na dotazu, který vrací col1 a col2 z T3 s jednou z výše uvedených klauzulí WHERE, s jedinečným seskupeným indexem zapnutým (col1, col2), takto:

VYTVOŘTE ZOBRAZENÍ dbo.T3CustomUnique POMOCÍ SCHEMABINDINGAS SELECT col1, col2 Z dbo.T3 KDE col1 NENÍ NULL NEBO col2 NENÍ NULL;PŘEJDĚTE VYTVOŘIT UNIKÁTNÍ CLUSTEROVANÝ INDEX idx_col1_col2 ON dbo.T3col1GOU 

Budete moci přidat více řádků s (NULL, NULL) v (sloupec1, sloupec2), ale nebudete moci přidat více výskytů jiných než NULL kombinací hodnot v (sloupec1, sloupec2), jako je (3 , NULL) nebo (NULL, 300) nebo (1, 100). Toto řešení však nepodporuje cizí klíč.

V tomto okamžiku spusťte následující kód pro vyčištění:

PUSTIT ZOBRAZENÍ, POKUD EXISTUJE dbo.T3CustomUnique;

Řešení 2 s použitím náhradního klíče a vypočítaného sloupce

Řešení s filtrovaným indexem a indexovaným zobrazením jsou dobrá, pokud nepotřebujete podporovat cizí klíč. Ale co když potřebujete prosadit referenční integritu? Jednou z možností je nadále používat řešení filtrovaného indexu nebo indexovaného zobrazení k vynucení jedinečnosti a používat spouštěče k vynucení referenční integrity. Tato možnost je však poměrně drahá.

Další možností je použít úplně jiné řešení pro část jedinečnosti, která cizí klíč podporuje. Řešení spočívá v přidání dvou sloupců do odkazované tabulky (v našem případě T3). Jeden sloupec s názvem id je náhradní klíč s vlastností identity. Další sloupec zvaný příznak je trvalý vypočítaný sloupec, který vrací id, když je sloupec1 NULL a 0, když není NULL. Potom vynutíte jedinečné omezení na kombinaci col1 a flag. Zde je kód pro přidání dvou sloupců a jedinečného omezení:

ALTER TABLE dbo.T3 ADD id INT NOT NULL IDENTITY, flag AS CASE WHEN col1 IS NULL THEN id ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3_col1_flag UNIQUE(col1, flag);

K dotazu T3 použijte následující kód:

SELECT * FROM dbo.T3;

Tento kód generuje následující výstup:

kol1 sloupec2 příznak id----------- ----------- ----------- ---------- -1 100 1 02 -1 2 0NULL -1 3 33 300 4 0NULL 400 5 5

Pokud jde o referenční tabulku (v našem případě T3FK), přidáte vypočítaný sloupec nazvaný flag, který je vždy nastaven na 0, a cizí klíč definovaný na (col1, příznak) ukazující na jedinečné sloupce T3 (col1, příznak), např. :

ALTER TABLE dbo.T3FK ADD flag AS 0 PERSISTED, CONSTRAINT FK_T3_T3FK CIZÍ KLÍČ(col1, flag) REFERENCE dbo.T3(col1, flag);

Pojďme toto řešení otestovat.

Zkuste přidat následující řádky:

INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES (1, 100, 'A'), (2, -1, 'B'), (3, 300, 'C');

Tyto řádky jsou úspěšně přidány, jak by měly, protože všechny mají odpovídající odkazované řádky.

Dotaz na tabulku T3FK:

SELECT * FROM dbo.T3FK;

Získáte následující výstup:

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

Zkuste přidat řádek, který nemá odpovídající řádek v odkazované tabulce:

INSERT INTO dbo.T3FK(col1, col2, othercol) VALUES (4, 400, 'D');

Pokus je odmítnut, jak by měl, s následující chybou:

Msg 547, Level 16, State 0
Příkaz INSERT byl v konfliktu s omezením FOREIGN KEY "FK_T3_T3FK". Ke konfliktu došlo v databázi "TSQLV5", tabulce "dbo.T3".

Zkuste přidat řádek do T3FK s NULL v col1:

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

Má se za to, že tento řádek nesouvisí s žádným řádkem v T3FK (volitelný vztah) a podle standardu by měl být povolen bez ohledu na to, zda v odkazované tabulce ve sloupci1 existuje NULL. T-SQL tento scénář podporuje a řádek byl úspěšně přidán.

Dotaz na tabulku T3FK:

SELECT * FROM dbo.T3FK;

Tento kód generuje následující výstup:

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

Řešení funguje dobře, když potřebujete vynutit standardní funkčnost jedinečnosti na jednom sloupci. Má to ale problém, když potřebujete vynutit jedinečnost na více sloupcích. Chcete-li problém demonstrovat, nejprve zrušte tabulky T3 a T3FK:

PUSTIT TABULKU, POKUD EXISTUJE dbo.T3FK, dbo.T3;

Pomocí následujícího kódu znovu vytvořte T3 se složeným jedinečným omezením (col1, col2, flag):

CREATE TABLE dbo.T3( col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, příznak JAKO CASE, KDYŽ col1 je NULL A col2 je NULL THEN id ELSE 0 END PERSISTED, CONSTRAINT UNQ_T3 UNIQUE, flag (col1, col2) ));

Všimněte si, že příznak je nastaven na id, když sloupec1 i sloupec2 jsou NULL a jinak 0.

Jedinečné omezení samo o sobě funguje dobře.

Spusťte následující kód a přidejte několik řádků do T3, včetně více výskytů (NULL, NULL) v (col1, col2):

INSERT INTO dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);

Tyto řádky jsou úspěšně přidány, jak by měly.

Zkuste přidat dva výskyty (1, NULL) v (col1, col2):

INSERT INTO dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);

Tento pokus se nezdaří s následující chybou:

Msg 2627, Level 14, State 1
Porušení omezení UNIQUE KEY 'UNQ_T3'. Nelze vložit duplicitní klíč do objektu 'dbo.T3'. Hodnota duplicitního klíče je (1, , 0).

Zkuste přidat dva výskyty (NULL, 100) v (col1, col2):

INSERT INTO dbo.T3(sloupec1, sloupec2) VALUES(NULL, 100),(NULL, 100);

Tento pokus také selže, jak by měl, s následující chybou:

Msg 2627, Level 14, State 1
Porušení omezení UNIQUE KEY 'UNQ_T3'. Nelze vložit duplicitní klíč do objektu 'dbo.T3'. Hodnota duplicitního klíče je (, 100, 0).

Zkuste přidat následující dva řádky, kde by nemělo dojít k žádnému porušení:

INSERT INTO dbo.T3(sloupec1, sloupec2) VALUES(3, NULL),(NULL, 300);

Tyto řádky byly úspěšně přidány.

Dotaz na tabulku T3 v tomto bodě:

SELECT * FROM dbo.T3;

Získáte následující výstup:

kol1 sloupec2 příznak id----------- ----------- ----------- ---------- -1 100 1 01 200 2 0NULL NULL 3 3NULL NULL 4 43 NULL 9 0NULL 300 10 0

Zatím je to dobré.

Dále spusťte následující kód a vytvořte tabulku T3FK se složeným cizím klíčem odkazujícím na jedinečné sloupce T3:

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, flag AS 0 PERSISTED, CONSTRAINT FKKTEIGNKEY, CONSTRAINT FKKTEIGNKEY, col3F ) ODKAZY dbo.T3(col1, col2, flag));

Toto řešení přirozeně umožňuje přidávání řádků do T3FK s (NULL, NULL) v (col1, col2). Problém je v tom, že také umožňuje přidání řádků NULL buď do sloupce1 nebo sloupce2, i když druhý sloupec není NULL a odkazovaná tabulka T3 takovou kombinaci kláves nemá. Zkuste například do T3FK přidat následující řádek:

INSERT INTO dbo.T3FK(sloupec1; sloupec2; jiný sloupec) VALUES(5; NULL; 'A');

Tento řádek je úspěšně přidán, i když v T3 není žádný související řádek. Podle normy by tento řádek neměl být povolen.

Zpět na rýsovací prkno…

Řešení 3 s použitím náhradního klíče a vypočítaného sloupce

Problém s předchozím řešením (řešení 2) nastává, když potřebujete podporovat složený cizí klíč. Umožňuje řádky v odkazující tabulce, které mají v seznamu jeden odkazující sloupec hodnotu NULL, i když v jiných odkazujících sloupcích nejsou hodnoty NULL a v odkazované tabulce není žádný související řádek. K vyřešení tohoto problému můžete použít variantu předchozího řešení, kterou budeme nazývat Řešení 3.

Nejprve použijte následující kód k odstranění existujících tabulek:

PUSTIT TABULKU, POKUD EXISTUJE dbo.T3FK, dbo.T3;

V novém řešení v odkazované tabulce (v našem případě T3) stále používáte sloupec náhradního klíče id založený na identitě. Můžete také použít trvalý vypočítaný sloupec s názvem unqpath. Když jsou všechny jedinečné sloupce (v našem příkladu col1 a col2) NULL, nastavíte unqpath na reprezentaci znakového řetězce id (žádné oddělovače ). Pokud některý z jedinečných sloupců není NULL, nastavíte unqpath na reprezentaci znakového řetězce odděleného seznamu jedinečných hodnot sloupců pomocí funkce CONCAT. Tato funkce nahradí hodnotu NULL prázdným řetězcem. Důležité je ujistit se, že používáte oddělovač, který se normálně nemůže objevit v samotných datech. Například s celočíselnými hodnotami col1 a col2 máte pouze číslice, takže by fungoval jakýkoli jiný oddělovač než číslice. V mém příkladu použiji tečku (.). Poté vynutíte jedinečné omezení na unqpath. Nikdy nebudete mít konflikt mezi hodnotou unqpath, když jsou všechny jedinečné sloupce NULL (nastaveny na id), a když žádný z jedinečných sloupců není NULL, protože v prvním případě unqpath neobsahuje oddělovač a v druhém případě ano. . Pamatujte, že řešení 3 použijete, pokud máte složené pouzdro na klíče, a pravděpodobně preferujete řešení 2, které je jednodušší, pokud máte pouzdro na klíče s jedním sloupcem. Pokud chcete řešení 3 použít také s klíčem s jedním sloupcem a ne řešením 2, ujistěte se, že přidáte oddělovač, když jedinečný sloupec není NULL, i když se jedná pouze o jednu hodnotu. Tímto způsobem nebudete mít konflikt, když se id v řádku, kde sloupec1 je NULL, rovná sloupci1 v jiném řádku, protože první nebude mít žádný oddělovač a druhý ano.

Zde je kód pro vytvoření T3 s výše uvedenými doplňky:

CREATE TABLE dbo.T3( col1 INT NULL, col2 INT NULL, id INT NOT NULL IDENTITY, unqpath AS CASE, KDYŽ col1 je NULL a col2 je 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));

Než se budeme zabývat cizím klíčem a referenční tabulkou, otestujme jedinečné omezení. Pamatujte, že by to mělo zakázat duplicitní kombinace hodnot jiných než NULL v jedinečných sloupcích, ale mělo by to umožnit více výskytů všech hodnot NULL v jedinečných sloupcích.

Spusťte následující kód a přidejte několik řádků, včetně dvou výskytů (NULL, NULL) v (col1, col2):

INSERT INTO dbo.T3(col1, col2) VALUES(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);

Tento kód se dokončí úspěšně, jak má.

Zkuste přidat dva výskyty (1, NULL) v (col1, col2):

INSERT INTO dbo.T3(col1, col2) VALUES(1, NULL),(1, NULL);

Tento kód se nezdaří s následující chybou, jak by měl:

Msg 2627, Level 14, State 1
Porušení omezení UNIQUE KEY 'UNQ_T3'. Nelze vložit duplicitní klíč do objektu 'dbo.T3'. Hodnota duplicitního klíče je (1.).

Podobně je odmítnut i následující pokus:

INSERT INTO dbo.T3(sloupec1, sloupec2) VALUES(NULL, 100),(NULL, 100);

Zobrazí se následující chyba:

Msg 2627, Level 14, State 1
Porušení omezení UNIQUE KEY 'UNQ_T3'. Nelze vložit duplicitní klíč do objektu 'dbo.T3'. Hodnota duplicitního klíče je (0,100).

Chcete-li přidat několik dalších řádků, spusťte následující kód:

INSERT INTO dbo.T3(sloupec1, sloupec2) VALUES(3, NULL),(NULL, 300);

Tento kód běží úspěšně, jak má.

V tomto okamžiku se zeptejte T3:

SELECT * FROM dbo.T3;

Získáte následující výstup:

col1 col2 id unqpath----------- ----------- ----------- ---------- -------------1 100 1 1,1001 200 2 1,200 NULL NULL 3 3NULL NULL 4 43 NULL 9 3.NULL 300 10 300

Sledujte hodnoty unqpath a ujistěte se, že rozumíte logice jejich konstrukce a rozdílu mezi případem, kdy všechny jedinečné sloupce mají hodnotu NULL (bez oddělovače), a případem, kdy alespoň jeden není NULL (oddělovač existuje).

Pokud jde o referenční tabulku, T3FK; definujete také vypočítaný sloupec nazvaný unqpath, ale v případě, že všechny odkazující sloupce mají hodnotu NULL, nastavíte sloupec na hodnotu NULL – nikoli na id. Když některý z odkazujících sloupců není NULL, vytvoříte stejný oddělený seznam hodnot jako v T3. Poté definujete cizí klíč na T3FK.unqpath ukazující na T3.unqpath, například takto:

CREATE TABLE dbo.T3FK( id INT NOT NULL IDENTITY CONSTRAINT PK_T3FK PRIMÁRNÍ KLÍČ, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL, unqpath JAKO PŘÍPAD, KDYŽ col1 JE NULL A col2 JE NULL (CAST(col1 AS VARCHAR(11)), '.', CAST(col2 AS VARCHAR(11))) END PERSISTED, CONSTRAINT FK_T3_T3FK CIZÍ KLÍČ(unqpath) REFERENCE dbo.T3(unqpath));

Tento cizí klíč odmítne řádky v T3FK, kde žádný z odkazujících sloupců není NULL a v odkazované tabulce T3 není žádný související řádek, jak ukazuje následující pokus:

INSERT INTO dbo.T3FK(sloupec1; sloupec2; jiný sloupec) VALUES(5; NULL; 'A');

Tento kód generuje následující chybu:

Msg 547, Level 16, State 0
Příkaz INSERT byl v konfliktu s omezením FOREIGN KEY "FK_T3_T3FK". Ke konfliktu došlo v databázi "TSQLV5", tabulce "dbo.T3", sloupci 'unqpath'.

Toto řešení bude řádky v T3FK, kde žádný z odkazujících sloupců není NULL, pokud existuje související řádek v T3, a také řádky s NULL ve všech odkazujících sloupcích, protože takové řádky se považují za nesouvisející s žádnými řádky v T3. Následující kód přidá takové platné řádky do T3FK:

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

Tento kód byl úspěšně dokončen.

Spusťte následující kód pro dotaz na T3FK:

SELECT * FROM dbo.T3FK;

Získáte následující výstup:

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 NULL 

Takže to chtělo trochu kreativity, ale teď máte řešení pro standardní jedinečné omezení, včetně podpory cizího klíče.

Závěr

Mysleli byste si, že jedinečné omezení je přímočará funkce, ale může to být trochu složitější, když potřebujete podporovat hodnoty NULL v jedinečných sloupcích. Je to složitější, když potřebujete implementovat standardní unikátní funkci omezení v T-SQL, protože tyto dva používají různá pravidla, pokud jde o to, jak zacházejí s hodnotami NULL. V tomto článku jsem vysvětlil rozdíl mezi těmito dvěma a poskytnutými řešeními, která fungují v T-SQL. Jednoduchý filtrovaný index můžete použít, když potřebujete vynutit jedinečnost pouze u jednoho sloupce s možností NULL a nepotřebujete podporovat cizí klíč, který na tento sloupec odkazuje. Pokud však potřebujete podporovat cizí klíč nebo složené jedinečné omezení se standardní funkcí, budete potřebovat složitější implementaci s náhradním klíčem a vypočítaným sloupcem.


  1. Jak získat připojovací řetězec z databáze

  2. Vysvětlení SQL Server (localdb)\v11.0

  3. MariaDB představí TO_CHAR()

  4. Funkce AVG() v MariaDB