mysql - 如何对多个 NULLABLE 列进行唯一索引约束?

标签 mysql sql

如果我用 NULLABLE 列设置唯一键索引,我可以插入任意多行:

create table routes (
   id bigint(20) NOT NULL AUTO_INCREMENT,
   firstname varchar(255) NOT NULL,
   lastname varchar(255) NOT NULL,
   departure DATE DEFAULT NULL,
   returndate DATE DEFAULT NULL,
   PRIMARY KEY (id),
   UNIQUE KEY uniq (firstname, lastname, departure)
)

为什么?更重要的是:尽管日期列可能为空(并且必须保持 DATE 类型),但我如何才能在日期列上保留唯一约束?

下面的sql语句实际上可以执行多次:

INSERT INTO `routes` (`firstname`, `lastname`, `departure`, `returndate`) 
       VALUES (NULL, 'john', 'doe', NULL, NULL);

最佳答案

为什么?因为 NULL = NULL 不是真的。

唯一性意味着它不允许另一行在一列中具有相同的值。但在 SQL 中,与任何其他值(包括另一个 NULL)相比,NULL 是“未知的”。三值 bool 逻辑就是这样定义的。

https://dev.mysql.com/doc/refman/8.0/en/create-index.html#create-index-unique说:

A UNIQUE index permits multiple NULL values for columns that can contain NULL.

如果执行两次 INSERT,您期望发生什么?即使“重复”为 NULL,它是否会导致重复键冲突?

您必须创建一个函数索引(在 MySQL 8.0.13 或更高版本上可用):

mysql> CREATE TABLE `routes` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `firstname` varchar(255) NOT NULL,
  `lastname` varchar(255) NOT NULL,
  `departure` date DEFAULT NULL,
  `returndate` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq` (`firstname`,`lastname`,(coalesce(`departure`, '1900-01-01')))
);

mysql> INSERT INTO `routes` (`id`, `firstname`, `lastname`, `departure`, `returndate`) 
    -> VALUES (NULL, 'john', 'doe', NULL, NULL);
Query OK, 1 row affected (0.00 sec)

mysql> INSERT INTO `routes` (`id`, `firstname`, `lastname`, `departure`, `returndate`) 
    ->  VALUES (NULL, 'john', 'doe', NULL, NULL);
ERROR 1062 (23000): Duplicate entry 'john-doe-1900-01-01' for key 'uniq'

关于您的评论:不,此功能索引中的 coalesce() 不会影响存储在列中的数据值,它只会影响索引的内容。

mysql> select * from routes;
+----+-----------+----------+-----------+------------+
| id | firstname | lastname | departure | returndate |
+----+-----------+----------+-----------+------------+
|  1 | john      | doe      | NULL      | NULL       |
+----+-----------+----------+-----------+------------+

如果你使用 MySQL 5.7+,你可以做类似的事情,但你必须创建一个虚拟列用于索引:

CREATE TABLE `routes` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `firstname` varchar(255) NOT NULL,
  `lastname` varchar(255) NOT NULL,
  `departure` date DEFAULT NULL,
  `departure_notnull` date GENERATED ALWAYS AS (coalesce(`departure`, '1900-01-01')) VIRTUAL,
  `returndate` date DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq` (`firstname`,`lastname`,`departure_notnull`)
)

如果您使用 MySQL 5.6 或更早版本,则不能使用虚拟列或功能索引。您必须使该列为 NOT NULL,并使用一个特殊值来表示它是一个非日期。

关于mysql - 如何对多个 NULLABLE 列进行唯一索引约束?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58267590/

相关文章:

mysql - 在行级别比较标记字段

php - 如何将 INTERVAL 参数与 PDO 绑定(bind)?

mysql - 有什么办法可以更好地优化这个全文MySQL搜索吗? CPU负载过高

sql - 数据库中聊天消息和评论的典型长度是多少?

sql - lib/pq postgres驱动程序中的连接泄漏

mysql - 使用 2 Where 子句更新列不同的表

java - MySQL 上的@GeneratedValue 多态抽象父类(super class)

javascript - 如何根据 mysql 数据库中的值在 html 中显示(加载)开/关按钮?

php - Laravel mysql迁移错误

sql - sqlite根据列条件创建新行