Database
 sql >> база данни >  >> RDS >> Database

NULL сложности – Част 4, Липсващо стандартно уникално ограничение

Тази статия е част 4 от поредица за NULL сложности. В предишните статии (Част 1, Част 2 и Част 3) разгледах значението на NULL като маркер за липсваща стойност, как се държат NULL при сравнения и в други елементи на заявката и стандартните функции за обработка на NULL, които не са все още наличен в T-SQL. Този месец разглеждам разликата между начина, по който се дефинира уникалното ограничение в стандарта ISO/IEC SQL и начина, по който работи в T-SQL. Също така ще предоставя персонализирани решения, които можете да приложите, ако имате нужда от стандартната функционалност.

Стандартно ограничение UNIQUE

SQL Server обработва NULL точно като стойности, различни от NULL, с цел налагане на уникално ограничение. Тоест, уникално ограничение за T е изпълнено, ако и само ако не съществуват два реда R1 и R2 от T, така че R1 и R2 да имат една и съща комбинация от NULL и стойности, различни от NULL в уникалните колони. Например, да предположим, че дефинирате уникално ограничение за col1, което е колона с NULL от тип данни INT. Опит за промяна на таблицата по начин, който би довел до повече от един ред с NULL в col1, ще бъде отхвърлен, точно както промяна, която би довела до повече от един ред със стойност 1 в col1, ще бъде отхвърлена.

Да предположим, че дефинирате съставно уникално ограничение за комбинацията от NULL INT колони col1 и col2. Опит за промяна на таблицата по начин, който би довел до повече от едно появяване на някоя от следните комбинации от (col1, col2) стойности, ще бъде отхвърлен:(NULL, NULL), (3, NULL), (NULL, 300 ), (1, 100).

Както можете да видите, T-SQL реализацията на уникалното ограничение третира NULL точно като стойности, различни от NULL с цел налагане на уникалност.

Ако искате да дефинирате външен ключ в някаква таблица X, препращаща към някаква таблица Y, трябва да наложите уникалност на реферираната колона(и) с една от следните опции:

  • Първичен ключ
  • Уникално ограничение
  • Нефилтриран уникален индекс

Първичният ключ не е разрешен в колони с NULL. Както уникално ограничение (което създава индекс под кориците), така и изрично създаден уникален индекс са разрешени за NULL колони и налагат тяхната уникалност в T-SQL с помощта на гореспоменатата логика. На референтната таблица е разрешено да има редове с NULL в референтната колона, независимо дали реферираната таблица има ред с NULL в колоната за препращане. Идеята е да се подкрепя една незадължителна връзка. Някои редове в таблицата за препращане може да са такива, които не са свързани с нито един ред в реферираната таблица. Ще приложите това, като използвате NULL в колоната за препращане.

За да демонстрирате T-SQL реализацията на уникално ограничение, изпълнете следния код, който създава таблица, наречена T3 с уникално ограничение, дефинирано в колоната NULLable INT col1, и я попълва с няколко примерни реда:

ИЗПОЛЗВАЙТЕ tempdb;GO ИЗПУСКАНЕ ТАБЛИЦА, АКО СЪЩЕСТВУВА dbo.T3; ИЗПОЛЗВАЙТЕ СЪЗДАВАЙТЕ ТАБЛИЦА dbo.T3(col1 INT NULL, col2 INT NULL, CONSTRAINT UNQ_T3 UNIQUE(col1)); ВМЕСТЕ В dbo.T3(col1, col2) СТОЙНОСТИ(1, 100),(2, -1),(NULL, -1),(3, 300);

Използвайте следния код, за да направите заявка за таблица:

ИЗБЕРЕТЕ * ОТ dbo.T3;

Тази заявка генерира следния изход:

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

Опитайте се да вмъкнете втори ред с NULL в col1:

ВЪВЕДЕТЕ В dbo.T3(col1, col2) СТОЙНОСТИ(NULL, 400);

Този опит е отхвърлен и получавате следната грешка:

Съобщение 2627, ниво 14, състояние 1
Нарушение на ограничението UNIQUE KEY 'UNQ_T3'. Не може да се вмъкне дублиран ключ в обект 'dbo.T3'. Стойността на дублирания ключ е ().

Стандартната уникална дефиниция на ограничението е малко по-различна от версията на T-SQL. Основната разлика е свързана с обработката NULL. Ето уникалната дефиниция на ограничение от стандарта:

„Уникално ограничение за T е изпълнено, ако и само ако не съществуват два реда R1 и R2 от T, така че R1 и R2 да имат еднакви стойности, различни от NULL, в уникалните колони.“

Така че таблица T с уникално ограничение за col1 ще позволи множество редове с NULL в col1, но ще забрани множество редове със същата стойност, различна от NULL в col1.

Това, което е малко по-трудно за обяснение, е какво се случва според стандарта със съставно уникално ограничение. Кажете, че имате уникално ограничение, дефинирано за (col1, col2). Можете да имате няколко реда с (NULL, NULL), но не можете да имате няколко реда с (3, NULL), точно както не можете да имате няколко реда с (1, 100). По същия начин не можете да имате няколко реда с (NULL, 300). Въпросът е, че не ви е позволено да имате няколко реда със същите стойности, различни от NULL, в уникалните колони. Що се отнася до външния ключ, можете да имате произволен брой редове в таблицата за препращане с NULL във всички референтни колони, независимо от това какво съществува в таблицата за препращане. Такива редове не са свързани с никакви редове в посочената таблица (по избор). Въпреки това, ако имате стойност, различна от NULL в някоя от колоните за препращане, трябва да съществува ред в таблицата, на която се препраща, със същите стойности, различни от NULL в колоните, за които се препраща.

Да предположим, че имате база данни в платформа, която поддържа стандартното уникално ограничение и трябва да мигрирате тази база данни към SQL Server. Може да се сблъскате с проблеми с налагането на уникални ограничения в SQL Server, ако уникалните колони поддържат NULL. Данните, които се считат за валидни в изходната система, могат да се считат за невалидни в SQL Server. В следващите раздели ще разгледам редица възможни решения в SQL Server.

Решение 1, използвайки филтриран индекс или индексиран изглед

Често срещано решение в T-SQL за прилагане на стандартната уникална функционалност на ограничението, когато има само една целева колона, е да се използва уникален филтриран индекс, който филтрира само редовете, където целевата колона не е NULL. Следният код премахва съществуващото уникално ограничение от T3 и имплементира такъв индекс:

ПРОМЕНЯ ТАБЛИЦА dbo.T3 ОГРАНИЧЕНИЕ ЗА ОТПУСКАНЕ UNQ_T3; СЪЗДАЙТЕ УНИКАЛЕН НЕКЛУСТРИРАН ИНДЕКС idx_col1_notnull НА dbo.T3(col1) КЪДЕТО col1 НЕ Е NULL;

Тъй като индексът филтрира само редове, където col1 не е NULL, неговото UNIQUE свойство се налага само върху стойностите, които не са NULL col1.

Припомнете си, че T3 вече има ред с NULL в col1. За да тествате това решение, използвайте следния код, за да добавите втори ред с NULL в col1:

ВЪВЕДЕТЕ В dbo.T3(col1, col2) СТОЙНОСТИ(NULL, 400);

Този код работи успешно.

Припомнете си, че T3 вече има ред със стойност 1 в col1. Изпълнете следния код, за да опитате да добавите втори ред с 1 в col1:

ВЪВЕДЕТЕ В dbo.T3(col1, col2) СТОЙНОСТИ(1, 500);

Както се очакваше, този опит се проваля със следната грешка:

Съобщение 2601, ниво 14, състояние 1
Не може да се вмъкне дублиран ключов ред в обект 'dbo.T3' с уникален индекс 'idx_col1_notnull'. Стойността на дублирания ключ е (1).

Използвайте следния код, за да потърсите T3:

ИЗБЕРЕТЕ * ОТ dbo.T3;

Този код генерира следния изход, показващ два реда с NULL в col1:

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

Това решение работи добре, когато трябва да наложите уникалност само на една колона и когато не е необходимо да налагате референтна цялост с външен ключ, сочещ към тази колона.

Проблемът с външния ключ е, че SQL Server изисква първичен ключ или уникално ограничение или уникален нефилтриран индекс, дефиниран в посочената колона. Не работи, когато има само уникален филтриран индекс, дефиниран в посочената колона. Нека се опитаме да създадем таблица с външен ключ, препращащ T3.col1. Първо, използвайте следния код, за да създадете таблица T3:

ПРОСТЪПНЕТЕ ТАБЛИЦА, АКО СЪЩЕСТВУВА dbo.T3FK;GO СЪЗДАЙТЕ ТАБЛИЦА dbo.T3FK( id INT NOT NULL ОГРАНИЧЕНИЕ НА ИДЕНТИЧНОСТ PK_T3FK ПЪРВИЧЕН КЛЮЧ, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) Npre> 

След това опитайте да изпълните следния код в опит да добавите външен ключ, сочещ от T3FK.col1 към T3.col1:

ПРОМЕНЯ ТАБЛИЦА dbo.T3FK ДОБАВЯНЕ НА ОГРАНИЧЕНИЕ FK_T3_T3FK ВЪНШЕН КЛЮЧ(col1) РЕФЕРЕНЦИИ dbo.T3(col1);

Този опит е неуспешен със следната грешка:

Съобщение 1776, ниво 16, състояние 0
Няма първични или кандидат ключове в посочената таблица 'dbo.T3', които да съответстват на списъка с референтни колони във външния ключ 'FK_T3_T3FK'.

Съобщение 1750, ниво 16, състояние 1
Не можа да се създаде ограничение или индекс. Вижте предишни грешки.

В този момент махнете съществуващия филтриран индекс за почистване:

ИЗПУСКАНЕ НА ИНДЕКС idx_col1_notnull НА dbo.T3;

Не изпускайте таблицата T3FK, тъй като ще я използвате в следващите примери.

Другият проблем с решението за филтриран индекс, ако приемем, че не се нуждаете от външен ключ, е, че не работи, когато трябва да наложите стандартната уникална функционалност на ограниченията върху множество колони, например върху комбинацията (col1, col2) . Не забравяйте, че стандартното уникално ограничение забранява дублиращи се комбинации от стойности, различни от NULL, в уникалните колони. За да приложите тази логика с филтриран индекс, трябва да филтрирате само редове, където някоя от уникалните колони не е NULL. Фразирано по различен начин, трябва да филтрирате само редове, които нямат NULL във всички уникални колони. За съжаление, филтрираните индекси позволяват само много прости изрази. Те не поддържат ИЛИ, НЕ или манипулация на колоните. Така че нито една от следните дефиниции на индекси в момента не се поддържа:

СЪЗДАДЕТЕ УНИКАЛЕН НЕКЛУСТРИРАН ИНДЕКС idx_customunique НА dbo.T3(col1, col2) КЪДЕТО col1 НЕ Е NULL ИЛИ col2 НЕ Е NULL; СЪЗДАЙТЕ УНИКАЛЕН НЕКЛУСТРИРАН ИНДЕКС idx_customunique НА dbo.T3(col1, col2) КЪДЕТО НЕ (col1 Е NULL И col2 Е NULL); СЪЗДАЙТЕ УНИКАЛЕН НЕКЛУСТРИРАН ИНДЕКС idx_customunique НА dbo.T3(col1, col2) КЪДЕТО COALESCE(col1, col2) НЕ Е NULL;

Обходното решение в такъв случай е да се създаде индексиран изглед въз основа на заявка, която връща col1 и col2 от T3 с една от клаузите WHERE по-горе, с уникален клъстериран индекс на (col1, col2), като така:

СЪЗДАДЕТЕ ИЗГЛЕД dbo.T3CustomUnique СЪС СХЕМИ ИЗБЕРЕТЕ col1, col2 ОТ dbo.T3 КЪДЕТО col1 НЕ Е NULL ИЛИ col2 НЕ Е NULL;GO СЪЗДАЙТЕ УНИКАЛЕН КЛУСТРИРАН ИНДЕКС idx_col1_col2); 

Ще ви бъде разрешено да добавяте няколко реда с (NULL, NULL) в (col1, col2), но няма да ви бъде разрешено да добавяте множество поява на различни от NULL комбинации от стойности в (col1, col2), като (3 , NULL) или (NULL, 300) или (1, 100). Все пак това решение не поддържа външен ключ.

В този момент изпълнете следния код за почистване:

ОТПУСКАНЕ НА ИЗГЛЕД, АКО СЪЩЕСТВУВА dbo.T3CustomUnique;

Решение 2, използващо сурогатен ключ и изчислена колона

Решенията с филтриран индекс и индексиран изглед са добри, стига да не е необходимо да поддържате външен ключ. Но какво ще стане, ако трябва да наложите референтната цялост? Една от опциите е да продължите да използвате филтрирания индекс или решението за индексиран изглед, за да наложите уникалност, и да използвате тригери за налагане на референтна цялост. Тази опция обаче е доста скъпа.

Друг вариант е да използвате напълно различно решение за частта за уникалност, която поддържа външен ключ. Решението включва добавяне на две колони към посочената таблица (T3 в нашия случай). Една колона, наречена id, е сурогатен ключ със свойство за идентичност. Друга колона, наречена флаг, е персистирана изчислена колона, която връща идентификатор, когато col1 е NULL и 0, когато не е NULL. След това налагате уникално ограничение върху комбинацията от col1 и флаг. Ето кода за добавяне на двете колони и уникалното ограничение:

ПРОМЕНЯ ТАБЛИЦА dbo.T3 ДОБАВЕТЕ id INT НЕ НУЛА ИДЕНТИЧНОСТ, флаг КАТО СЛУЧАЙ, КОГАТО col1 Е NULL, ТОГАВА идентификатор ELSE 0 КРАЙ, ОГРАНИЧЕНИЕ UNQ_T3_col1_flag UNIQUE(col1, флаг);

Използвайте следния код, за да потърсите T3:

ИЗБЕРЕТЕ * ОТ dbo.T3;

Този код генерира следния изход:

col1 col2 id флаг ----------- ----------- ----------- ---------- -1 100 1 02 -1 2 0NULL -1 3 33 300 4 0NULL 400 5 5

Що се отнася до референтната таблица (T3FK в нашия случай), вие добавяте изчислена колона, наречена flag, която винаги е зададена на 0, и външен ключ, дефиниран на (col1, flag), сочещ към уникалните колони на T3 (col1, flag), като така :

ПРОМЕНЯТ ТАБЛИЦА dbo.T3FK ДОБАВЯНЕ НА флаг КАТО 0 УДЪРЖАВА, ОГРАНИЧЕНИЕ FK_T3_T3FK ВЪНШЕН КЛЮЧ(col1, флаг) РЕФЕРЕНЦИИ dbo.T3(col1, флаг);

Нека тестваме това решение.

Опитайте се да добавите следните редове:

ВЪВЕТЕ В dbo.T3FK(col1, col2, othercol) СТОЙНОСТИ (1, 100, 'A'), (2, -1, 'B'), (3, 300, 'C');
>

Тези редове са добавени успешно, както трябва, тъй като всички имат съответни реферирани редове.

Запитване в таблицата T3FK:

ИЗБЕРЕТЕ * ОТ dbo.T3FK;

Получавате следния изход:

id col1 col2 othercol флаг----------- ----------- ----------- --------- - -----------1 1 100 A 02 2 -1 B 03 3 300 C 0

Опитайте се да добавите ред, който няма съответен ред в посочената таблица:

ВЪВЕТЕ В dbo.T3FK(col1, col2, othercol) СТОЙНОСТИ (4, 400, 'D');

Опитът е отхвърлен, както трябва да бъде, със следната грешка:

Msg 547, ниво 16, състояние 0
Изразът INSERT е в конфликт с ограничението FOREIGN KEY "FK_T3_T3FK". Конфликтът е възникнал в база данни "TSQLV5", таблица "dbo.T3".

Опитайте да добавите ред към T3FK с NULL в col1:

ВЪВЕТЕ В dbo.T3FK(col1, col2, othercol) СТОЙНОСТИ (NULL, NULL, 'E');

Счита се, че този ред не е свързан с нито един ред в T3FK (незадължителна връзка) и според стандарта трябва да бъде разрешен, независимо дали съществува NULL в посочената таблица в колона 1. T-SQL поддържа този сценарий и редът е добавен успешно.

Запитване в таблицата T3FK:

ИЗБЕРЕТЕ * ОТ dbo.T3FK;

Този код генерира следния изход:

id col1 col2 othercol флаг----------- ----------- ----------- --------- - -----------1 1 100 A 02 2 -1 B 03 3 300 C 05 NULL NULL E 0

Решението работи добре, когато трябва да наложите стандартната функционалност за уникалност върху една колона. Но има проблем, когато трябва да наложите уникалност на множество колони. За да демонстрирате проблема, първо пуснете таблиците T3 и T3FK:

ПРОСТАНЕ ТАБЛИЦА, АКО СЪЩЕСТВУВА dbo.T3FK, dbo.T3;

Използвайте следния код, за да пресъздадете T3 със съставно уникално ограничение за (col1, col2, флаг):

СЪЗДАДЕТЕ ТАБЛИЦА dbo.T3( col1 INT NULL, col2 INT NULL, id INT НЕ НУЛА ИДЕНТИФИКАЦИЯ, флаг КАТО СЛУЧАЙ, КОГАТО col1 Е NULL И col2 Е NULL ТОГАВА id ELSE 0 END PERSISTED, CONSTRAINT UNQUETE, 2 col3 ));

Забележете, че флагът е зададен на id, когато и col1 и col2 са NULL и 0 в противен случай.

Самото уникално ограничение работи добре.

Изпълнете следния код, за да добавите няколко реда към T3, включително множество поява на (NULL, NULL) в (col1, col2):

ВМЕСЕТЕ В dbo.T3(col1, col2) СТОИНИ(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);

Тези редове са добавени успешно, както трябва.

Опитайте да добавите две поява на (1, NULL) в (col1, col2):

ВЪВЕТЕ В dbo.T3(col1, col2) STONYS(1, NULL),(1, NULL);

Този опит се проваля както трябва със следната грешка:

Съобщение 2627, ниво 14, състояние 1
Нарушение на ограничението UNIQUE KEY 'UNQ_T3'. Не може да се вмъкне дублиран ключ в обект 'dbo.T3'. Стойността на дублирания ключ е (1, , 0).

Опитайте да добавите две поява на (NULL, 100) в (col1, col2):

ВМЕСЕТЕ В dbo.T3(col1, col2) STOJI(NULL, 100),(NULL, 100);

Този опит също се проваля както би трябвало със следната грешка:

Съобщение 2627, ниво 14, състояние 1
Нарушение на ограничението UNIQUE KEY 'UNQ_T3'. Не може да се вмъкне дублиран ключ в обект 'dbo.T3'. Стойността на дублирания ключ е (, 100, 0).

Опитайте да добавите следните два реда, където не трябва да има нарушение:

ВМЕСЕТЕ В dbo.T3(col1, col2) STOJI(3, NULL),(NULL, 300);

Тези редове са добавени успешно.

Потърсете таблицата T3 в този момент:

ИЗБЕРЕТЕ * ОТ dbo.T3;

Получавате следния изход:

col1 col2 id флаг ----------- ----------- ----------- ---------- -1 100 1 01 200 2 0NULL NULL 3 3NULL NULL 4 43 NULL 9 0NULL 300 10 0

Дотук добре.

След това изпълнете следния код, за да създадете таблицата T3FK със съставен външен ключ, препращащ уникалните колони на T3:

СЪЗДАВАНЕ НА ТАБЛИЦА dbo.T3FK( id INT NOT NULL ОГРАНИЧЕНИЕ НА ИДЕНТИЧНОСТ PK_T3FK ПЪРВИЧЕН КЛЮЧ, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) NOT NULL, флаг КАТО 0 PERSISTED, CONSTRAINT_1 CONSTRAINT FKl ) РЕФЕРЕНЦИИ dbo.T3(col1, col2, флаг));

Това решение естествено позволява добавяне на редове към T3FK с (NULL, NULL) в (col1, col2). Проблемът е, че той също така позволява добавяне на редове с NULL в col1 или col2, дори когато другата колона не е NULL и посочената таблица T3 няма такава клавишна комбинация. Например, опитайте да добавите следния ред към T3FK:

ВЪВЕДЕТЕ В dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');

Този ред е добавен успешно, въпреки че няма свързан ред в T3. Според стандарта този ред не трябва да се допуска.

Обратно към чертожната дъска...

Решение 3, използващо сурогатен ключ и изчислена колона

Проблемът с предишното решение (Решение 2) възниква, когато трябва да поддържате съставен външен ключ. Той позволява редове в таблицата за препращане, които имат NULL в списъка с една референтна колона, дори когато има стойности, различни от NULL в други колони за препращане, и няма свързан ред в препращащата таблица. За да се справите с това, можете да използвате вариант на предишното решение, което ще наречем Решение 3.

Първо, използвайте следния код, за да премахнете съществуващите таблици:

ПРОСТАНЕ ТАБЛИЦА, АКО СЪЩЕСТВУВА dbo.T3FK, dbo.T3;

В новото решение в посочената таблица (T3 в нашия случай) все още използвате колоната за заместващ ключ, базиран на самоличност. Вие също така използвате постоянна изчислена колона, наречена unqpath. Когато всички уникални колони (col1 и col2 в нашия пример) са NULL, вие задавате unqpath на символен низ представяне на идентификатор (без разделители ). Когато някоя от уникалните колони не е NULL, вие задавате unqpath на символен низ представяне на отделен списък с уникални стойности на колони, като използвате функцията CONCAT. Тази функция замества NULL с празен низ. Важното е да се уверите, че използвате разделител, който обикновено не може да се появи в самите данни. Например, с цели числа col1 и col2 имате само цифри, така че всеки разделител, различен от цифра, ще работи. В моя пример ще използвам точка (.). След това налагате уникално ограничение върху unqpath. Никога няма да имате конфликт между стойността unqpath, когато всички уникални колони са NULL (зададени на id) спрямо когато някоя от уникалните колони не е NULL, тъй като в първия случай unqpath не съдържа разделител, а във втория случай го прави . Не забравяйте, че ще използвате Решение 3, когато имате композитен ключов случай и вероятно предпочитате Решение 2, което е по-просто, когато имате ключов случай с една колона. Ако искате да използвате Решение 3 също с ключ с една колона, а не Решение 2, просто се уверете, че добавяте разделителя, когато уникалната колона не е NULL, въпреки че има само една стойност. По този начин няма да имате конфликт, когато идентификаторът в ред, където col1 е NULL, е равен на col1 в друг ред, тъй като първият няма да има разделител, а вторият ще има.

Ето кода за създаване на T3 с гореспоменатите допълнения:

СЪЗДАДЕТЕ ТАБЛИЦА dbo.T3( col1 INT NULL, col2 INT NULL, id INT НЕ НУЛА ИДЕНТИФИКАЦИЯ, unqpath КАТО СЛУЧАЙ, КОГАТО col1 Е NULL И col2 Е NULL ТОГАВА CAST(id КАТО VARCHAR(10)) ELSE CONCAT( КАТО VARCHAR(11)), '.', CAST(col2 КАТО VARCHAR(11))) КРАЙ ПОСТОЙНО, ОГРАНИЧЕНИЕ UNQ_T3 UNIQUE(unqpath));

Преди да се занимаваме с външен ключ и референтната таблица, нека тестваме уникалното ограничение. Не забравяйте, че трябва да забранява дублиращи се комбинации от стойности, различни от NULL, в уникалните колони, но трябва да позволява многократно появяване на всички NULL в уникалните колони.

Изпълнете следния код, за да добавите няколко реда, включително две поява на (NULL, NULL) в (col1, col2):

ВМЕСЕТЕ В dbo.T3(col1, col2) СТОИНИ(1, 100),(1, 200),(NULL, NULL),(NULL, NULL);

Този код завършва успешно както трябва.

Опитайте се да добавите две поява на (1, NULL) в (col1, col2):

ВЪВЕТЕ В dbo.T3(col1, col2) STONYS(1, NULL),(1, NULL);

Този код се проваля със следната грешка, както трябва:

Съобщение 2627, ниво 14, състояние 1
Нарушение на ограничението UNIQUE KEY 'UNQ_T3'. Не може да се вмъкне дублиран ключ в обект 'dbo.T3'. Стойността на дублирания ключ е (1.).

По същия начин се отхвърля и следният опит:

ВМЕСЕТЕ В dbo.T3(col1, col2) STOJI(NULL, 100),(NULL, 100);

Получавате следната грешка:

Съобщение 2627, ниво 14, състояние 1
Нарушение на ограничението UNIQUE KEY 'UNQ_T3'. Не може да се вмъкне дублиран ключ в обект 'dbo.T3'. Стойността на дублирания ключ е (.100).

Изпълнете следния код, за да добавите още няколко реда:

ВМЕСЕТЕ В dbo.T3(col1, col2) STOJI(3, NULL),(NULL, 300);

Този код работи успешно както трябва.

В този момент потърсете T3:

ИЗБЕРЕТЕ * ОТ dbo.T3;

Получавате следния изход:

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 .300

Наблюдавайте стойностите на unqpath и се уверете, че разбирате логиката зад тяхната конструкция и разликата между случай, при който всички уникални колони са NULL (без разделител) спрямо когато поне една не е NULL (съществува разделител).

Що се отнася до референтната таблица, T3FK; вие също така дефинирате изчислена колона, наречена unqpath, но в случай, когато всички рефериращи колони са NULL, вие задавате колоната на NULL - не на id. Когато някоя от колоните за препращане не е NULL, вие изграждате същия разделен списък със стойности, както направихте в T3. След това дефинирате външен ключ на T3FK.unqpath, сочещ към T3.unqpath, така:

СЪЗДАДЕТЕ ТАБЛИЦА dbo.T3FK( id INT NOT NULL ОГРАНИЧЕНИЕ НА ИДЕНТИЧНОСТ PK_T3FK ПЪРВИЧЕН КЛЮЧ, col1 INT NULL, col2 INT NULL, othercol VARCHAR(10) НЕ НУЛИ, unqpath КАТО СЛУЧАЙ, КОГАТО КОЛИЧЕСТВО КОЛИЧЕСТВО КОЛИЧЕСТВО 1 И NULL NULL IS (CAST(col1 КАТО VARCHAR(11)), '.', CAST(col2 КАТО VARCHAR(11))) КРАЙ ПОСТОЙНО, ОГРАНИЧЕНИЕ FK_T3_T3FK ВЪНШЕН КЛЮЧ(unqpath) РЕФЕРЕНЦИИ dbo.T3(unqpath));

Този външен ключ ще отхвърли редове в T3FK, където някоя от колоните за препращане не е NULL и няма свързан ред в посочената таблица T3, както показва следният опит:

ВЪВЕДЕТЕ В dbo.T3FK(col1, col2, othercol) VALUES(5, NULL, 'A');

Този код генерира следната грешка:

Msg 547, ниво 16, състояние 0
Изразът INSERT е в конфликт с ограничението FOREIGN KEY "FK_T3_T3FK". Конфликтът е възникнал в база данни "TSQLV5", таблица "dbo.T3", колона "unqpath".

Това решение ще има редове в T3FK, където нито една от референтните колони не е NULL, стига да съществува свързан ред в T3, както и редове с NULL във всички рефериращи колони, тъй като такива редове се считат за несвързани с нито един ред в T3. Следният код добавя такива валидни редове към T3FK:

ВМЕСЕТЕ В dbo.T3FK(col1, col2, othercol) СТОЙНОСТИ (1 , 100 , 'A'), (1 , 200 , 'B'), (3 , NULL, 'C'), (NULL, 300 , 'D'), (NULL, NULL, 'E'), (NULL, NULL, 'F');

Този код завършва успешно.

Изпълнете следния код, за да заявите T3FK:

ИЗБЕРЕТЕ * ОТ dbo.T3FK;

Получавате следния изход:

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 

Така че отне малко креативност, но сега имате решение за стандартното уникално ограничение, включително поддръжка на външни ключове.

Заключение

Бихте си помислили, че уникалното ограничение е проста функция, но може да стане малко сложно, когато трябва да поддържате NULL в уникалните колони. Става по-сложно, когато трябва да внедрите стандартната уникална функционалност на ограниченията в T-SQL, тъй като двете използват различни правила по отношение на това как обработват NULL. В тази статия обясних разликата между двете и предоставих заобиколни решения, които работят в T-SQL. Можете да използвате прост филтриран индекс, когато трябва да наложите уникалност само на една колона с NULL и не е необходимо да поддържате външен ключ, който препраща към тази колона. Ако обаче трябва да поддържате външен ключ или съставно уникално ограничение със стандартната функционалност, ще ви е необходима по-сложна реализация със сурогатен ключ и изчислена колона.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Слайд тестове и проби от #SQLintersection

  2. Инсталиране на WordPress с помощта на WP-CLI

  3. Нотация на Чен

  4. Prisma, как да изчистя базата данни

  5. Как да свържете база данни с Amazon VPC