NULL的唯一键

问题描述 投票:35回答:8

这个问题需要一些假设的背景。让我们考虑一个employee表,其中包含namedate_of_birthtitlesalary,使用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

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

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说,“因为我不知道Jim Johnson的NULL出生日期尚未出现在这张桌子上,所以我会加上他。”

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

mysql database null relational-model
8个回答
22
投票

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

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

  • 一种方式,错误的方式,是使用一些魔术日期来表示未知。这只是让你通过DBMS“问题”,但没有从逻辑上解决问题。预计出生日期未知的两个“John Smith”条目存在问题。这些人是同一个人还是他们独一无二的人?如果您知道它们不同,那么您又回到了同样的问题 - 您的唯一键并不是唯一的。甚至不要考虑指定一系列魔术日期来代表“未知” - 这真的是通向地狱的道路。
  • 更好的方法是将EmployeeId属性创建为代理键。这只是您分配给您知道唯一的个人的任意标识符。该标识符通常只是一个整数值。然后创建一个Employee表,将EmployeeId(唯一的,不可为空的键)与您认为是依赖属性的内容相关联,在本例中为Name和Date of Birth(其中任何一个都可以为空)。在您之前使用名称/出生日期的任何地方使用EmployeeId代理键。这会为您的系统添加一个新表,但会以稳健的方式解决未知值的问题。

6
投票

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

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


5
投票

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

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

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


3
投票

还有另一种方法可以做到这一点。添加列(不可为空)以表示date_of_birth列的String值。如果date_of_birth为null,则新列值将为“”(空字符串)。

我们将该列命名为date_of_birth_str并创建一个唯一约束employee(name,date_of_birth_str)。因此,当两个recored具有相同的名称和null date_of_birth值时,唯一约束仍然有效。

但应仔细考虑维护两个相同含义列的努力,以及新列的性能损害。


1
投票

我建议创建额外的表格列checksum,它将包含namedate_of_birth的md5哈希值。删除独特的密钥(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
投票

完美的解决方案是支持基于函数的UK,但随着mySQL也需要支持基于函数的索引,这变得更加复杂。这样可以避免使用“假”值代替NULL,同时还允许开发人员决定如何处理UK中的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()函数)


0
投票

我有一个类似的问题,但有一个扭曲。在您的情况下,每个员工都有一个生日,虽然可能是未知的。在这种情况下,系统为具有未知生日但其他方面相同信息的员工分配两个值具有逻辑意义。 NealB接受的答案非常准确。

但是,我遇到的问题是数据字段不一定有值的问题。例如,如果向表中添加了“name_of_spouse”字段,则表中的每一行都不一定有值。在这种情况下,NealB的第一个要点(“错误的方式”)实际上是有道理的。在这种情况下,对于没有已知配偶的每一行,应在列name_of_spouse中插入字符串“None”。

遇到这个问题的情况是编写一个带有数据库的程序来分类IP流量。目标是在专用网络上创建IP流量图。每个数据包都放入一个数据库表中,该数据库表具有基于其ip源和dest,端口源和dest,传输协议和应用程序协议的唯一连接索引。但是,许多数据包根本没有应用程序协议。例如,所有没有应用程序协议的TCP数据包都应归为一类,并且应该占用连接索引中的一个唯一条目。这是因为我希望这些数据包形成我的图形的单个边缘。在这种情况下,我从上面提出了自己的建议,并在应用程序协议字段中存储了一个字符串“None”,以确保这些数据包形成一个唯一的组。


-2
投票

简单来说,唯一约束的作用是制作字段或列。 null将销毁此属性,因为数据库将null视为未知

为了避免重复并允许null:

将唯一键设为主键

© www.soinside.com 2019 - 2024. All rights reserved.