Что не так с обнуляемыми столбцами в составных первичных ключах? - PullRequest
136 голосов
/ 22 декабря 2008

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

В то же время большинство систем также допускают уникальные ограничения на обнуляемые столбцы.

Почему уникальные ограничения могут иметь значения NULL, а первичные ключи - нет? Есть ли фундаментальная логическая причина для этого или это скорее техническое ограничение?

Ответы [ 6 ]

195 голосов
/ 22 декабря 2008

Первичные ключи предназначены для уникальной идентификации строк. Это делается путем сравнения всех частей ключа с вводом.

По определению, NULL не может быть частью успешного сравнения. Даже сравнение с самим собой (NULL = NULL) не удастся. Это означает, что ключ, содержащий NULL, не будет работать.

Кроме того, NULL допускается во внешнем ключе для обозначения необязательного отношения. (*) Если разрешить его в PK, это будет нарушено.


(*) Предостережение: наличие внешних ключей, допускающих значение NULL, не является чистым дизайном реляционной базы данных.

Если есть две сущности A и B, где A может быть необязательно связан с B, чистое решение - создать таблицу разрешения (скажем, AB). Эта таблица будет связывать A с B: если является отношением, то она будет содержать запись, если не , то не будет.

55 голосов
/ 22 декабря 2008

Первичный ключ определяет уникальный идентификатор для каждой строки в таблице: если у таблицы есть первичный ключ, у вас есть гарантированный способ выбрать любую строку из нее.

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

43 голосов
/ 20 апреля 2013

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

Рассмотрим случай версий модулей / пакетов, хранящихся в виде последовательности полей:

CREATE TABLE module
  (name        varchar(20) PRIMARY KEY,
   description text DEFAULT '' NOT NULL);

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20),
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

Первые 5 элементов первичного ключа являются регулярно определяемыми частями версии выпуска, но некоторые пакеты имеют настраиваемое расширение, которое обычно не является целым числом (например, «rc-foo», «vanilla», «beta» или что-то в этом роде). еще кто-то, кому не хватает четырех полей, может придумать). Если пакет не имеет расширения, то в приведенной выше модели он равен NULL, и если оставить все как есть, никакого вреда не будет.

Но что является НУЛЬ? Предполагается, что он представляет собой отсутствие информации, неизвестного. Тем не менее, возможно, это имеет больше смысла:

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20) DEFAULT '' NOT NULL,
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

В этой версии "ext" часть кортежа NOT NULL, но по умолчанию это пустая строка, которая семантически (и практически) отличается от NULL. NULL - это неизвестное, тогда как пустая строка - это преднамеренная запись «чего-то, чего нет». Другими словами, «пустой» и «ноль» - это разные вещи. Разница между «у меня нет значения здесь» и «я не знаю, каково значение здесь».

Когда вы регистрируете пакет, в котором отсутствует расширение версии, вы знаете, , в нем отсутствует расширение, поэтому пустая строка на самом деле является правильным значением. Значение NULL будет правильным только в том случае, если вы не знаете, есть ли у него расширение или нет, или вы знали, что оно имеет, но не знали, что это такое. С такой ситуацией легче справиться в системах, где строковые значения являются нормой, потому что нет способа представить «пустое целое число», кроме вставки 0 или 1, которое будет свернуто в любых последующих сравнениях (которые имеют свои последствия) *.

Кстати, оба способа допустимы в Postgres (поскольку мы обсуждаем RDMBS "на уровне предприятия"), но результаты сравнения могут сильно отличаться, когда вы добавляете NULL в смесь - потому что NULL == "не знаю "так что все результаты сравнения, включающие NULL, заканчиваются тем, что NULL, поскольку вы не можете знать то, что неизвестно. ОПАСНОСТЬ! Тщательно подумайте об этом: это означает, что результаты сравнения NULL распространяются через серию сравнений. Это может быть источником незаметных ошибок при сортировке, сравнении и т. Д.

Postgres предполагает, что вы взрослый человек, и можете принять это решение для себя. Oracle и DB2 предполагают, что вы не понимаете, что делаете что-то глупое, и выдают ошибку. Это обычно правильная вещь, но не всегда - вы могли бы на самом деле не знать и иметь NULL в некоторых случаях и, следовательно, оставить строку с неизвестным элементом, с которым имеют место значимые сравнения невозможно правильное поведение.

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

[* ПРИМЕЧАНИЕ. Можно создать пользовательский тип, представляющий собой объединение целых чисел и «нижнего» типа, который будет семантически означать «пустой», а не «неизвестный». К сожалению, это вносит некоторую сложность в операции сравнения, и, как правило, правильность ввода текста на практике не стоит усилий, так как вам вообще не следует разрешать много значений NULL. Тем не менее, было бы замечательно, если бы СУБД включали тип BOTTOM по умолчанию в дополнение к NULL, чтобы предотвратить привычку случайно связывать семантику «нет значения» с «неизвестным значением». ]

18 голосов
/ 22 декабря 2008

NULL == NULL -> false (хотя бы в СУБД)

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

4 голосов
/ 25 февраля 2015

Ответ Тони Эндрюса приличный. Но реальный ответ заключается в том, что это соглашение использовалось сообществом реляционных баз данных и НЕ является необходимостью. Может быть, это хорошее соглашение, а может и нет.

Сравнение чего-либо со значением NULL приводит к НЕИЗВЕСТНОМУ (третье значение истинности). Таким образом, как было предложено с нулями, вся традиционная мудрость относительно равенства уходит в окно. Ну вот как это выглядит на первый взгляд.

Но я не думаю, что это обязательно так, и даже базы данных SQL не считают, что NULL уничтожает все возможности для сравнения.

Запустите в вашей базе данных запрос ВЫБРАТЬ * ИЗ ЗНАЧЕНИЙ (NULL) UNION SELECT * FROM VALUES (NULL)

То, что вы видите, это просто один кортеж с одним атрибутом, который имеет значение NULL. Таким образом, объединение распознало здесь два значения NULL как равные.

При сравнении составного ключа, который имеет 3 компонента, с кортежем с 3 атрибутами (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 AND 3 = 3 AND NULL = NULL Результат этого НЕИЗВЕСТНЫЙ.

Но мы могли бы определить новый тип оператора сравнения, например. ==. X == Y <=> X = Y ИЛИ (X НУЛЬ И Y НУЛЬ)

Наличие такого типа оператора равенства сделает составные ключи с нулевыми компонентами или несоставные ключи с нулевым значением беспроблемными.

0 голосов
/ 14 июня 2017

Я все еще верю, что это фундаментальный / функциональный недостаток, вызванный техническими особенностями. Если у вас есть необязательное поле, с помощью которого вы можете идентифицировать клиента, вам теперь нужно ввести в него фиктивное значение, просто потому, что NULL! = NULL, не особенно элегантно, хотя это «отраслевой стандарт»

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...