Почему ограничение UNIQUE допускает только один NULL?

36

Технически, NULL = NULL является False, по этой логике никакой NULL не равен ни одному NULL, и все NULL различны. Разве это не означает, что все NULL являются уникальными, а уникальный индекс должен разрешать любое количество NULL?

user87166
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
Пол Уайт говорит, что 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, но это одна из тех вещей, которые они сделали, чтобы сделать его более полезным, даже если это не «технически» правильно. Обратите внимание, что PRIMARY KEY (также уникальный индекс) не допускает NULL (конечно).

Кеннет Фишер
источник
1
Эта техническая (SQL-Server) также не соответствует стандарту 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 Моя любимая цитата о нуле: Луи Дэвидсон, «Профессиональный дизайн базы данных SQL Server 2000», Wrox Press, 2001, стр. 52. «Сводится к одному предложению: NULL - зло».

Гринстоун Уолкер
источник
1
Разрешение одного nullне достигает этой цели. Потому что пропущенное значение может оказаться таким же, как значение в одной из других строк.
Мартин Смит
1
Что сказал @MartinSmith. Что делать, если у вас есть ограничение проверки CHECK (Value IN ('A','B','C','D'))? Тогда и реализация SQL-Server, и стандарт SQL позволяют таблице иметь 5 строк (по одной строке для каждого значения плюс 1 с NULL.) Тогда, возможно, хотя база данных соответствует ее ограничениям, она не соответствует намерению разработчика таблица должна иметь максимум 4 строки. Нет значения, на которое можно изменить значение NULL, которое не будет нарушать ограничение, если только одна или несколько строк не будут удалены.
ypercubeᵀᴹ
1
Тот факт, что стандарт разрешил бы 6 и 106 строк вместо 5, не меняет того, что оба они в некоторой степени терпят неудачу в этом сценарии.
ypercubeᵀᴹ
@ Мартин Смит, может, но с другой стороны, может и нет - сервер базы данных не может сказать, поэтому он не рискует и выбирает безопасный маршрут. Это то, что решили программисты 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, что означает неизвестность.

Ограничение Unique потребовало бы определенного значения для сравнения значений столбца. Другими словами, при сравнении значения одного столбца с любым другим значением столбца с использованием оператора равенства он должен иметь значение false, чтобы быть действительным. Неизвестное на самом деле не является ложным, хотя его часто считают ложным. Два значения NULL могут быть равными или нет ... это просто невозможно определить окончательно.

Это помогает думать об уникальном ограничении как об ограничении значений, которые могут быть определены как отличные друг от друга. Под этим я подразумеваю, что вы запускаете SELECT, который выглядит примерно так:

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

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

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

EricJ
источник
Ваш выбор даст 1 результат, когда есть ограничение Unique (в любой реализации, не только SQL-Server). Какова ваша точка зрения?
ypercubeᵀᴹ
-3

Одной из основных целей UNIQUEограничения является предотвращение дублирования записей. Если необходимо иметь таблицу, в которой может быть несколько записей, где значение «неизвестно», но две записи не могут иметь одно и то же «известное» значение, тогда неизвестным значениям следует присвоить искусственные уникальные идентификаторы, прежде чем они будут добавлен в таблицу.

Есть несколько редких случаев, когда столбец имеет UNIQUEограничение и содержит одно нулевое значение; например, если таблица содержит отображение между значениями столбцов и локализованными текстовыми описаниями, строка для NULLпозволит определить описание, которое должно отображаться, когда этот столбец находится в какой-то другой таблице NULL. ПоведениеNULL позволяет для этого случая использования.

В противном случае я не вижу оснований для базы данных с UNIQUEограничением на какой-либо столбец, позволяющей существование множества идентичных записей, но я не вижу способа предотвратить это, допуская при этом несколько записей, значения ключей которых не различимы. Объявление, что NULLоно не равно самому себе, не сделает NULLзначения отличимыми друг от друга.

Supercat
источник
3
Искусственные уникальные идентификаторы - шутка, извините. Как ты собираешься сделать это для VIN? Если вы не знаете, что это такое, зачем придумывать что-то? Просто чтобы занять дополнительное место на диске? Кажется бессмысленным обходить некоторые другие проблемы (например, нежелание писать приложение таким образом, чтобы оно изящно обрабатывало NULL). Если вам абсолютно необходимо знать, почему что-то имеет значение NULL (существует, но неизвестно, и знать, что его не существует, или не знать или не беспокоиться о его существовании, например), то добавьте столбец статуса. Токены просто приводят к неуклюжим кодам, чтобы справиться с ними.
Аарон Бертран
Многое зависит от цели ограничения уникальности. Если поле будет использоваться в качестве идентификатора, оно не должно быть нулевым. В случаях (как с VIN), когда бизнес-правила предполагают, что, когда элемент появляется дважды, один из них должен быть неправильным, но некоторые элементы могут быть «не знаю», ограничение уникальности не похоже на правильный подход. Если у кого-то есть автомобиль с известным VIN, и он конфликтует с другим в базе данных, он может знать, что хотя бы один из VIN неверен, но было бы лучше, чтобы база данных сообщала о предполагаемом значении для обеих записей, чем предположение это правильно.
суперкат
@AaronBertrand: в некоторых случаях поле null-null unique-if-not-null, возможно, должно быть суррогатным ключом, не может быть установлено до заполнения поля (например, «идентификатор супруга»), но в таких ситуациях, как что «уникальное» ограничение будет недостаточным; было бы необходимо, чтобы, если X.Spouse отличен от NULL, X.Spouse.Spouse = X. Между прочим, что-то вроде «супруга» также может быть обработано, говоря, что запись для не состоящего в браке человека должна иметь не «NULL» в качестве супруга, а скорее его собственный идентификатор, в этом случае правило X.spouse.spouse = X может относиться ко всем.
суперкат