Уникальный ключ с NULL

Этот вопрос требует некоторого гипотетического фона. Рассмотрим таблицу employee с столбцами name, date_of_birth, title, salary, используя MySQL как СУБД. Поскольку, если какое-либо данное лицо имеет то же имя и дату рождения, что и другое лицо, они по определению являются тем же лицом (за исключением удивительных совпадений, когда у нас есть два человека по имени Авраам Линкольн, родившийся 12 февраля 1809 года), мы поставим уникальный ключ на name и date_of_birth, что означает "не хранить одного и того же человека дважды". Теперь рассмотрим эти данные:

id name        date_of_birth title          salary
 1 John Smith  1960-10-02    President      500,000
 2 Jane Doe    1982-05-05    Accountant      80,000
 3 Jim Johnson NULL          Office Manager  40,000
 4 Tim Smith   1899-04-11    Janitor         95,000

Если я сейчас попытаюсь запустить следующий оператор, он должен и будет терпеть неудачу:

INSERT INTO employee (name, date_of_birth, title, salary)
VALUES ('Tim Smith', '1899-04-11', 'Janitor', '95,000')

Если я попробую это, он будет успешным:

INSERT INTO employee (name, title, salary)
VALUES ('Jim Johnson', 'Office Manager', '40,000')

И теперь мои данные будут выглядеть так:

id name        date_of_birth title          salary
 1 John Smith  1960-10-02    President      500,000
 2 Jane Doe    1982-05-05    Accountant      80,000
 3 Jim Johnson NULL          Office Manager  40,000
 4 Tim Smith   1899-04-11    Janitor         95,000
 5 Jim Johnson NULL          Office Manager  40,000

Это не то, что я хочу, но я не могу сказать, что полностью не согласен с тем, что произошло. Если мы говорим в терминах математических множеств,

{'Tim Smith', '1899-04-11'} = {'Tim Smith', '1899-04-11'} <-- TRUE
{'Tim Smith', '1899-04-11'} = {'Jane Doe', '1982-05-05'} <-- FALSE
{'Tim Smith', '1899-04-11'} = {'Jim Johnson', NULL} <-- UNKNOWN
{'Jim Johnson', NULL} = {'Jim Johnson', NULL} <-- UNKNOWN

Я предполагаю, что MySQL говорит: "Поскольку я не знаю, что Джим Джонсон с датой рождения NULL еще не в этой таблице, я добавлю его".

Мой вопрос: Как предотвратить дубликаты, даже если date_of_birth не всегда известен?. Лучшее, что я до сих пор придумал, - переместить date_of_birth в другую таблицу. Однако проблема заключается в том, что я могу, в конечном итоге, с двумя кассирами с тем же именем, титулом и зарплатой, разными датами рождения и без возможности их сохранения без дубликатов.

Ответ 1

Основное свойство уникального ключа заключается в том, что он должен быть уникальным. Выполнение части ключа Nullable уничтожает это свойство.

В вашей проблеме есть два возможных решения:

  • Один из способов, неверный путь - использовать какую-то волшебную дату для представления неизвестного. Это просто вас ждет СУБД "проблема", но не решает проблему в логическом смысле. Ожидайте проблем с двумя записями "Джон Смит" с неизвестными датами рождения. Являются ли эти парни одним и тем же или являются уникальными людьми? Если вы знаете, что они разные, вы вернулись к той же старой проблеме - ваш уникальный ключ просто не уникален. Даже не думайте о назначении целого ряда волшебных дат представлять "неизвестные" - это действительно путь в ад.

  • Лучше всего создать атрибут EmployeeId в качестве суррогатного ключа. Это просто произвольный идентификатор, который вы назначаете отдельным лицам, которых вы знаете уникальны. Эта Идентификатор часто является целым числом. Затем создайте таблицу Employee, чтобы связать EmployeeId (уникальный, не нулевой ключ) к тому, что вы считаете зависимыми атрибутами, в этом случае Имя и дата рождения (любая из которых может быть недействительной). Используйте ключ суррогата EmployeeId везде, где вы ранее использовалось имя/дата рождения. Это добавляет новую таблицу в вашу систему, но эффективно решает проблему неизвестных значений.

Ответ 2

Я думаю, что MySQL делает это прямо здесь. Некоторые другие базы данных (например, Microsoft SQL Server) рассматривают NULL как значение, которое можно вставить только один раз в столбец UNIQUE, но лично я считаю это странным и неожиданным поведением.

Однако, поскольку это то, что вы хотите, вы можете использовать некоторое "магическое" значение вместо NULL, например, дату в прошлом

Ответ 3

Ваша проблема не иметь дубликатов на основе имени не разрешима, потому что у вас нет естественного ключа. Ввод фальшивой даты для людей, чья дата рождения неизвестна, не решит вашу проблему. Джон Смит, родившийся 1900/01/01, по-прежнему будет отличным человеком, чем Джон Смит, родившийся в 1960/03/09.

Я ежедневно работаю с именами из больших и малых организаций, и могу заверить вас, что у них есть два разных человека с одинаковым именем все время. Иногда с тем же названием. Дата рождения также не является гарантией уникальности, много Джона Смита, родившегося в ту же дату. Если мы работаем с офисными данными врачей, у нас часто есть два врача с таким же именем, адресом и номером телефона (комбинации отца и сына).

Лучше всего иметь идентификатор сотрудника, если вы вставляете данные о сотрудниках, чтобы идентифицировать каждого сотрудника однозначно. Затем проверьте уникальное имя в пользовательском интерфейсе, и если есть одно или несколько совпадений, спросите пользователя, если он имел в виду их, и если он говорит "нет", вставьте запись. Затем создайте процесс разорения, чтобы исправить проблемы, если кто-то случайно назначил два идентификатора.

Ответ 4

Есть другой способ сделать это. Добавление столбца (non-nullable) для представления значения String для столбца date_of_birth. Новое значение столбца будет "" (пустая строка), если date_of_birth имеет значение null.

Мы называем столбец как date_of_birth_str и создаем уникального сотрудника ограничения (name, date_of_birth_str). Поэтому, когда два повторных вызова имеют одно и то же имя и значение null date_of_birth, уникальное ограничение все еще работает.

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

Ответ 5

Идеальное решение будет поддерживать функции, основанные на Великобритании, но это становится более сложным, поскольку mySQL также должен будет поддерживать индексы на основе функций. Это предотвратит необходимость использования "поддельных" значений вместо NULL, а также позволит разработчикам решить, как обрабатывать значения NULL в Великобритании. К сожалению, mySQL в настоящее время не поддерживает такую ​​функциональность, о которой я знаю, поэтому у нас остались обходные пути.

CREATE TABLE employee( 
 name CHAR(50) NOT NULL, 
 date_of_birth DATE, 
 title CHAR(50), 
 UNIQUE KEY idx_name_dob (name, IFNULL(date_of_birth,'0000-00-00 00:00:00'))
);

(Обратите внимание на использование функции IFNULL() в определении уникального ключа)

Ответ 6

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

Чтобы избежать дублирования и разрешить null:

Сделайте уникальный ключ как Основной ключ

Ответ 7

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

Однако проблема, с которой я столкнулся, - это та, в которой поле данных не обязательно имеет значение. Например, если вы добавили поле "name_of_spouse" в свою таблицу, не обязательно было бы значение для каждой строки таблицы. В этом случае первая точка Bullet NealB ( "неправильный путь" ) имеет смысл. В этом случае строка "Нет" должна быть вставлена ​​в столбец name_of_spouse для каждой строки, в которой не было известного супруга.

Ситуация, в которой я столкнулся с этой проблемой, заключалась в написании программы с базой данных для классификации IP-трафика. Целью было создание графика IP-трафика в частной сети. Каждый пакет был помещен в таблицу базы данных с уникальным индексом соединения на основе его источника ip и dest, источника порта и протокола dest, транспортного протокола и протокола приложения. Однако многие пакеты просто не имеют протокола приложения. Например, все TCP-пакеты без протокола приложения должны быть классифицированы вместе и должны занимать одну уникальную запись в индексе соединений. Это связано с тем, что я хочу, чтобы эти пакеты формировали единый край моего графика. В этой ситуации я взял свой собственный совет сверху и сохранил строку "Нет" в поле протокола приложения, чтобы гарантировать, что эти пакеты сформировали уникальную группу.