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

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

36 голосов
/ 02 ноября 2010

Этот вопрос требует некоторого гипотетического фона.Давайте рассмотрим таблицу 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 на другой стол.Однако проблема в том, что я могу получить, скажем, двух кассиров с одинаковым именем, названием и зарплатой, разными датами рождения и без возможности хранить их обоих без дубликатов.

Ответы [ 8 ]

23 голосов
/ 03 ноября 2010

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

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

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

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

6 голосов
/ 02 ноября 2010

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

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

5 голосов
/ 03 ноября 2010

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

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

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

3 голосов
/ 03 ноября 2010

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

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

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

1 голос
/ 28 февраля 2018

Рекомендую создать дополнительный столбец таблицы checksum, который будет содержать хэш md5 name и date_of_birth.Удалите уникальный ключ (name, date_of_birth), потому что он не решает проблему.Создайте один уникальный ключ на контрольной сумме.

ALTER TABLE employee 
    ADD COLUMN checksum CHAR(32) NOT NULL;

UPDATE employee 
SET checksum = MD5(CONCAT(name, IFNULL(date_of_birth, '')));

ALTER TABLE employee 
    ADD UNIQUE (checksum);

Это решение создает небольшие технические издержки, поскольку для каждой вставленной пары необходимо создавать хэш (то же самое для каждого поискового запроса).Для дальнейших улучшений вы можете добавить триггер, который будет генерировать хеш для вас при каждой вставке:

CREATE TRIGGER before_insert_employee 
BEFORE INSERT ON employee
FOR EACH ROW
    IF new.checksum IS NULL THEN
      SET new.checksum = MD5(CONCAT(new.name, IFNULL(new.date_of_birth, '')));
    END IF;
0 голосов
/ 08 сентября 2017

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

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

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

0 голосов
/ 06 мая 2015

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

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

Сделать уникальный ключ Первичный ключ

0 голосов
/ 22 октября 2011

Идеальным решением была бы поддержка основанных на функциях британских программ, но это становится более сложным, поскольку тогда 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 () вопределение уникального ключа)

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