57

这个问题需要一些假设的背景。让我们考虑一个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

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

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出生日期的 Jim Johnson 不在此表中,所以我将添加他。”

我的问题是:即使date_of_birth并不总是知道,我如何才能防止重复?到目前为止,我想出的最好的办法是搬到date_of_birth另一张桌子上。然而,这样做的问题是,我最终可能会得到两个名字、头衔和薪水相同、出生日期不同的收银员,并且无法在没有重复的情况下存储它们。

4

11 回答 11

33

唯一键的一个基本属性是它必须是唯一的。使该键的一部分为 Nullable 会破坏此属性。

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

  • 一种方法,错误的方法是使用一些神奇的日期来表示未知。这只是让您摆脱 DBMS“问题”,但并不能从逻辑意义上解决问题。预计两个出生日期未知的“John Smith”条目会出现问题。这些人是同一个人还是独特的个体?如果您知道它们不同,那么您又回到了同一个老问题-您的唯一密钥不是唯一的。甚至不要考虑分配整个范围的魔术日期来表示“未知”——这确实是通往地狱的道路。

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

于 2010-11-03T15:31:53.223 回答
7

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

但是,由于这是您想要的,您可以使用一些“神奇”值而不是 NULL,例如过去很长时间的日期

于 2010-11-02T20:29:27.150 回答
7

我建议创建额外的表列checksum,其中包含name和的 md5 哈希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;
于 2018-02-28T08:23:33.157 回答
5

您没有基于名称的重复项的问题无法解决,因为您没有自然键。为出生日期不详的人输入假日期并不能解决您的问题。出生于 1900 年 1 月 1 日的约翰史密斯仍将是一个不同于出生于 1960 年 3 月 9 日的约翰史密斯的人。

我每天都在处理来自大小组织的姓名数据,我可以向你保证,他们总是有两个不同的人同名。有时具有相同的职位。生日也不能保证唯一性,很多约翰·史密斯都是同一天出生的。哎呀,当我们处理医生办公室数据时,我们经常有两个名字、地址和电话号码相同的医生(父子组合)

如果您要插入员工数据以唯一标识每个员工,最好的办法是拥有员工 ID。然后检查用户界面中的唯一名称,如果有一个或多个匹配项,请询问用户他是不是故意的,如果他说不是,则插入记录。如果有人意外分配了两个 id,则构建一个 deupping 过程来解决问题。

于 2010-11-03T14:50:29.103 回答
3

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

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

但是对于两个同义柱的维护工作,以及新柱的性能危害,应该慎重考虑。

于 2010-11-03T01:56:45.550 回答
1

您可以添加一个生成的列,其中NULL值被一个未使用的常量替换,例如零。然后您可以将唯一约束应用于此列:

CREATE TABLE employee ( 
  name VARCHAR(50) NOT NULL, 
  date_of_birth DATE, 
  uq_date_of_birth DATE AS (IFNULL(date_of_birth, '0000-00-00')) UNIQUE
);
于 2019-12-24T00:45:18.400 回答
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()函数)

于 2011-10-21T20:25:17.450 回答
0

我有一个类似的问题,但有一个转折。在您的情况下,每个员工都有生日,尽管它可能是未知的。在这种情况下,系统为生日未知但信息相同的员工分配两个值是合乎逻辑的。NealB 接受的答案非常准确。

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

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

于 2017-09-08T13:56:25.443 回答
0

我正在寻找一种解决方案,Alexander Yancharuk建议对我来说是个好主意。但在我的情况下,我的列是外键,employee_id 可以为空。

我有这个结构:


+----+---------+-------------+
| id | room_id | employee_id |
+----+---------+-------------+
|  1 |       1 | NULL        |
|  2 |       2 | 1           |
+----+---------+-------------+

并且employee_id为NULL的room_id不能重复

我解决了在插入之前添加触发器的问题,如下所示:

DELIMITER $$
USE `db`$$
CREATE DEFINER=`root`@`%` TRIGGER `db`.`room_employee` BEFORE INSERT ON `room_employee` FOR EACH ROW
BEGIN
    IF EXISTS (
            SELECT room_id, employee_id
            FROM room_employee
            WHERE (NEW.room_id = room_employee.room_id AND NEW.employee_id IS NULL AND room_employee.employee_id IS NULL)
        ) THEN
        CALL `The room Can not be duplicated on room employee table`;
    END IF;
END$$
DELIMITER ;

我还为room_idemployee_id添加了唯一的约束

于 2020-11-06T11:30:29.810 回答
0

我认为这里的基本问题是你的实际意思

插入员工(姓名、职务、薪水)值('Jim Johnson'、'Office Manager'、'40,000')

你自己对一个人的定义是姓名和出生日期,那么这个陈述在这种情况下意味着什么?我想说,解决您的问题的方法是通过在您的 name 和 date_of_birth 列上添加 NOT NULL 来禁止插入半身份,例如上面的身份。这样,该语句将失败并强制您输入完整的身份,并且唯一密钥将完成其工作以防止您两次输入同一个人。

于 2022-02-08T01:15:27.787 回答
-3

简单来说,Unique 约束的作用就是使字段或列。null会破坏此属性,因为数据库将 null 视为未知

为了避免重复并允许为空:

将唯一键设为主

于 2015-05-06T09:43:59.150 回答