Чому UNIQUE обмеження дозволяє лише один NULL?


36

Технічно NULL = NULL є хибним, за цією логікою жоден NULL не дорівнює NULL, і всі NULL є різними. Чи не повинно це означати, що всі NULL є унікальними, а унікальний індекс повинен дозволяти будь-яку кількість NULL?


Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Пол Білий каже, що GoFundMonica

Відповіді:


52

Чому це працює саме так? Тому що назад, коли хтось прийняв дизайнерське рішення, не знаючи і не піклуючись про те, що говорить стандарт (адже ми маємо всілякі дивні форми поведінки з NULLs і можемо примусити різну поведінку за бажанням). Це рішення продиктувало, що в цьому випадку NULL = NULL.

Це було не дуже розумне рішення. Що вони повинні були зробити, це те, щоб поведінка за замовчуванням дотримувалася стандарту ANSI, і якщо вони дійсно хотіли такої своєрідної поведінки, дозвольте це за допомогою параметра DDL типу WITH CONSIDER_NULLS_EQUALабо WITH ALLOW_ONLY_ONE_NULL.

Звичайно, задній огляд - 20/20.

І зараз у нас є рішення, навіть якщо це не найчистіше чи найінтуїтивніше.

Ви можете отримати належну поведінку ANSI в SQL Server 2008 і вище, створивши унікальний, відфільтрований індекс.

CREATE UNIQUE INDEX foo ON dbo.bar(key) WHERE key IS NOT NULL;

Це дозволяє більше, ніж одне NULLзначення, оскільки ці рядки повністю залишені під час перевірки дублікатів. Як додатковий бонус, це в кінцевому підсумку буде меншим індексом, ніж той, який складався з усієї таблиці, якщо NULLбуло дозволено кілька s (особливо коли це не єдиний стовпець в індексі, він містить INCLUDEстовпці тощо). Однак, можливо, вам потрібно знати про деякі інші обмеження відфільтрованих індексів:


8

Правильно. Реалізація унікального обмеження або індексу на сервері sql дозволяє отримати один і єдиний NULL. Також правильно, що це технічно не відповідає визначенню NULL, але це одна з тих речей, які вони зробили, щоб зробити його більш корисним, хоча це не "технічно" правильно. Зверніть увагу, що ПЕРШИЙ КЛЮЧ (також унікальний індекс) не дозволяє NULL (звичайно).


1
Ця технічність (SQL-сервера) також не відповідає стандарту SQL. З цього питання є 7-річний елемент Connect .
ypercubeᵀᴹ

@ypercube Правда. Ось чому я сказав, що це просто реалізація і насправді не відповідає визначенню NULL. Я не думав про відфільтрований унікальний індекс (хоча я використовував його для інших речей.)
Кеннет Фішер

3

По-перше - перестаньте використовувати фразу "Нульове значення", це просто зведе вас з пустощі. Натомість використовуйте словосполучення "нульовий маркер" - маркер у стовпці, який вказує на те, що фактичне значення в цьому стовпці або відсутнє, або непридатне (але зауважте, що маркер не говорить про те, який із цих варіантів насправді є¹).

Тепер уявіть собі наступне (де база даних не має повних знань про модельовану ситуацію).

Situation          Database

ID   Code          ID   Code
--   -----         --   -----
1    A             1    A
2    B             2    (null)
3    C             3    C
4    B             4    (null)

Правило цілісності, яке ми моделюємо, - «Кодекс повинен бути унікальним». Реальна ситуація порушує це, тому база даних не повинна дозволяти обом пунктам 2 і 4 одночасно знаходитись у таблиці.

Найбезпечнішим і найменш гнучким підходом було б забороняти нульові маркери в полі Код, тому немає можливості суперечливих даних. Найбільш гнучким підходом було б дозволити кілька нульових маркерів і турбуватися про унікальність при введенні значень.

Програмісти Sybase пішли з дещо безпечним, не дуже гнучким підходом, дозволяючи лише один нульовий маркер у таблиці - щось з того часу скаржилися коментатори. Microsoft продовжував цю поведінку, я думаю, що для зворотної сумісності.


¹ Я впевнений, що десь читав, що Кодд розглядав можливість впровадження двох нульових маркерів - одного для невідомого, одного для непридатного - але відхилив його, але не можу знайти посилання. Я правильно пам’ятаю?

PS Моя улюблена цитата про null: Луї Девідсон, "Професійний дизайн баз даних SQL Server 2000", Wrox Press, 2001, стор. 52. "Зведено до одного речення: NULL - це зло".


1
Дозвіл одного nullне досягає і цієї мети. Тому що відсутнє значення може виявитись таким же, як значення в одному з інших рядків.
Мартін Сміт

1
Що сказав @MartinSmith. Що робити, якщо у вас є обмеження чека CHECK (Value IN ('A','B','C','D'))? Тоді як реалізація SQL-сервера, так і стандарт SQL дозволяють таблиці мати 5 рядків (один рядок для кожного значення плюс 1 з NULL.) Тоді, мабуть, хоча база даних узгоджується зі своїми обмеженнями, вона не відповідає намірам дизайнера для таблиця повинна мати максимум 4 ряди. Немає значення, що NULL може бути змінено на таке, що не порушить обмеження, якщо не буде видалено один або кілька рядків.
ypercubeᵀᴹ

1
Той факт, що стандарт дозволить 6 навіть 106 рядків замість 5, не змінюється, що вони обоє певним чином провалюються в цьому сценарії.
ypercubeᵀᴹ

@Martin Smith, можливо, але знову ж таки, це не може - сервер бази даних не може сказати, тому він не ризикує і бере безпечний маршрут. Саме так вирішили програмісти Sybase (я припускаю), викликаючи роздратування з цього часу (принаймні, що стосується Inside SQL Server 6.5, найдавнішої книги на моїй книжковій полиці, де Рон Саукуп робить такий же коментар, що й Аарон Бертран у своїй відповіді) . Я думаю, що може бути і гірше - вони не могли мати жодних нульових маркерів. :-)
Грінстоун Уокер

2
@GreenstoneWalker - це не "безпечний" маршрут. Це передбачає, що відсутнє значення не буде конфліктувати. CREATE TABLE #T(A INT NULL UNIQUE);INSERT INTO #T VALUES (1),(NULL);UPDATE #T SET A = 1 WHERE A IS NULL;призведе до помилки. Згідно з вашою теорією дизайнерських мотивацій, NULLу першому випадку слід було б перешкодити введенню - адже неповні знання означають, що немає гарантії того, що значення є іншим.
Мартін Сміт

2

Це може бути не технічно точно, але по-філософськи це допомагає мені спати вночі ...

Як і декілька інших, на які говорили або на які згадувалося, якщо ви вважаєте, що NULL є невідомим, то ви не можете визначити, чи є одне значення NULL насправді іншим NULL. Думаючи про це таким чином, вираз NULL == NULL слід оцінювати до NULL, тобто невідомо.

Унікальне обмеження потребує остаточного значення для порівняння значень стовпців. Іншими словами, при порівнянні одного значення стовпця з будь-яким іншим значенням стовпця, використовуючи оператор рівності, він повинен оцінювати, що значення false відповідає дійсності. Невідомий насправді неправдивий, хоча до нього часто ставляться як до хитрості. Два значення NULL можуть бути рівними, чи ні ... їх просто неможливо визначити остаточно.

Це допомагає мислити унікальне обмеження як обмежуючі значення, які можна визначити як відмінні одне від одного. Що я маю на увазі під цим, якщо ви запустите SELECT, який виглядає приблизно так:

SELECT * from dbo.table1 WHERE ColumnWithUniqueContraint="some value"

Більшість людей очікували б одного результату, враховуючи, що існує унікальне обмеження. Якщо ви дозволили декілька значень NULL у ColumnWithUniqueConstraint, то неможливо було б вибрати один окремий рядок із таблиці, використовуючи NULL як порівняне значення.

Враховуючи це, я вважаю, що незалежно від того, точно реалізовано чи ні щодо визначення NULL, це, безумовно, набагато практичніше в більшості ситуацій, ніж допускати кілька значень NULL.


Ваш Select дасть 1 результат, коли існує унікальне обмеження (у будь-якій реалізації, не тільки SQL-сервер). Який твій погляд?
ypercubeᵀᴹ

-3

Однією з головних цілей UNIQUEобмеження є запобігання повторюваних записів. Якщо потрібно мати таблицю, в якій може бути кілька записів, де значення "невідомо", але жодним записам не дозволяється мати однакове "відоме" значення, то невідомим значенням слід присвоїти штучні унікальні ідентифікатори, перш ніж вони будуть додано до таблиці.

Є кілька рідкісних випадків, коли стовпець, який має UNIQUEобмеження і містить єдине нульове значення; Наприклад, якщо таблиця містить відображення між значеннями стовпців та локалізованими текстовими описами, рядок для NULLдасть змогу визначити опис, який повинен з’являтися, коли цей стовпець у якійсь іншій таблиці є NULL. Поведінка NULLдозволу для цього випадку використання.

В іншому випадку я не бачу підстав для бази даних із UNIQUEобмеженням для будь-якого стовпця, що дозволяло б існувати безліч ідентичних записів, але я не бачу способу запобігти цьому, дозволяючи кілька записів, ключові значення яких не можна відрізняти. Заявивши, що NULLне є рівними собі, NULLзначення не відрізнятимуться один від одного.


3
Штучні унікальні ідентифікатори - це жарт, вибачте. Як ти будеш робити це для VIN? Якщо ви не знаєте, що це таке, навіщо щось робити? Просто, щоб зайняти додатковий простір на диску? Здається, нісенітниця вирішує якусь іншу проблему (як, не бажаючи писати додаток таким чином, щоб воно витончено обробляло NULL). Якщо вам абсолютно потрібно знати, чому щось є NULL (існує, але невідомо порівняно, знаєте, що воно не існує проти, не знаю, або не цікаво, якщо воно існує, наприклад), додайте якийсь стовпець статусу. Токени просто призводять до незграбного прокручування коду для боротьби з ними.
Аарон Бертран

Багато що залежить від мети обмеження унікальності. Якщо поле буде використано як ідентифікатор, воно не повинно бути нульовим. У випадках (як це стосується VIN), коли правила ведення бізнесу дозволяють припустити, що коли предмет з’являється двічі, один з них повинен бути помилковим, але деякі елементи можуть бути "не знаю", обмеження унікальності не відчуває себе як належний підхід. Якщо у вас є транспортний засіб з відомим VIN, і він конфліктує з іншим у базі даних, можна знати, що принаймні один з VIN помиляється, але було б краще, щоб база даних повідомляла вірогідне значення для обох записів, ніж здогадка той прав.
supercat

@AaronBertrand: Є деякі випадки, коли поле, можливо, унікальне, якщо не є нульовим, повинно бути сурогатним ключем, не може бути встановлено до заповнення поля (наприклад, "ідентифікатор подружжя"), але в таких ситуаціях, як що "унікального" обмеження буде недостатньо; необхідно, що якщо X.Spouse не є нульовим, X.Spouse.Spouse = X. Між іншим, щось на зразок "подружжя" може також оброблятися, кажучи, що запис для незаміжньої особи не повинен мати "NULL" як подружжя, а власний ідентифікатор, і в цьому випадку правило X.spouse.spouse = X може стосуються всіх.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.