NULL的唯一键

Jas*_*ett 38 mysql database null relational-model

这个问题需要一些假设的背景.让我们考虑一个employee有列的表name,date_of_birth,title,salary使用MySQL作为RDBMS.因为如果任何一个人的姓名和出生日期与另一个人相同,那么根据定义,他们是同一个人(除非我们有两个人在1809年2月12日出生的亚伯拉罕·林肯出生的惊人巧合),我们将把独特的按键namedate_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
Run Code Online (Sandbox Code Playgroud)

如果我现在尝试运行以下语句,它应该会失败:

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

如果我尝试这个,它会成功:

INSERT INTO employee (name, title, salary)
VALUES ('Jim Johnson', 'Office Manager', '40,000')
Run Code Online (Sandbox Code Playgroud)

现在我的数据将如下所示:

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
Run Code Online (Sandbox Code Playgroud)

这不是我想要的,但我不能说我完全不同意发生的事情.如果我们谈论数学集,

{'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
Run Code Online (Sandbox Code Playgroud)

我的猜测是,MySQL说,"因为我不知道NULL出生日期的吉姆约翰逊还不在这张桌子上,所以我会加上他."

我的问题是:即使date_of_birth并不总是知道重复,我怎么能防止重复?到目前为止,我提出的最好的办法就是搬到date_of_birth另一张桌子.然而,问题在于,我可能最终得到两个具有相同名称,头衔和工资,不同出生日期的收银员,并且无法在没有重复的情况下存储它们.

Nea*_*alB 23

唯一键的基本属性是它必须是唯一的.制作关键Nullable的一部分会破坏这个属性.

您的问题有两种可能的解决方案:

  • 一种方式,错误的方式,是使用一些魔术日期来表示未知.这只是让你通过DBMS"问题",但没有从逻辑上解决问题.预计出生日期未知的两个"John Smith"条目存在问题.这些人是同一个人还是他们独一无二的人?如果您知道它们不同,那么您又回到了同样的问题 - 您的唯一键并不是唯一的.甚至不要考虑指定一系列魔术日期来代表"未知" - 这真的是通向地狱的道路.

  • 更好的方法是将EmployeeId属性创建为代理键.这只是您分配给您知道唯一的个人的任意标识符.该标识符通常只是一个整数值.然后创建一个Employee表,将EmployeeId(唯一的,不可为空的键)与您认为是依赖属性的内容相关联,在本例中为Name和Date of Birth(其中任何一个都可以为空).在您之前使用名称/出生日期的任何地方使用EmployeeId代理键.这会为您的系统添加一个新表,但会以稳健的方式解决未知值的问题.


Ale*_*ruk 7

我建议创建额外的表列checksum其中将包含MD5哈希namedate_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);
Run Code Online (Sandbox Code Playgroud)

该解决方案会产生很小的技术开销,导致您需要为每个插入的对生成哈希(每个搜索查询都是如此)。为了进一步改进,您可以添加触发器,在每次插入时为您生成哈希:

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;
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个解决方案。但是,与其使用触发器,不如将校验和存储为[生成列](https://dev.mysql.com/doc/refman/8.0/en/create-table- generated-columns.html )?我想到的是“CREATE TABLE”或“ALTER TABLE”命令中的“CHAR(32) AS (MD5(CONCAT(name, IFNULL(date_of_birth, '')))) STORED”。 (4认同)

Mar*_*ers 6

我认为MySQL就是在这里做的.其他一些数据库(例如Microsoft SQL Server)将NULL视为只能插入UNIQUE列的值,但我个人认为这是一种奇怪且意外的行为.

但是,因为这是你想要的,你可以使用一些"魔术"值而不是NULL,例如过去很久的日期

  • 我并不一定不同意MySQL正确处理这个问题.最终结果不是我想要的结果:我最终得到重复,这是不可接受的.而对我来说,一个"神奇"的价值只是一个"假的虚假".没有冒犯,但我觉得有点难以忍受,这是正确的方法. (2认同)

HLG*_*GEM 5

您没有基于名称的重复项的问题是不可解决的,因为您没有自然键.为出生日期未知的人提供假日期并不能解决您的问题.1900年1月1日出生的约翰史密斯仍然是一个与1960/03/09出生的约翰史密斯不同的人.

我每天都使用来自大型和小型组织的名称数据,我可以向您保证,他们总是有两个不同的名字.有时候有相同的职称.出生日并不能保证独特性,很多约翰史密斯出生于同一天.当我们与医生办公室数据合作时,我们经常有两名医生,他们的姓名,地址和电话号码相同(父子组合)

如果要插入员工数据以唯一地标识每个员工,最好的办法是拥有一个员工ID.然后检查用户界面中的uniquename以及是否有一个或多个匹配项,询问用户是否表示他们,如果他说不,请插入记录.然后构建一个deupping进程来解决问题,如果有人偶然分配了两个id.