How to do unique constraint works with NULL value in MySQL

后端 未结 2 432
执念已碎
执念已碎 2020-12-16 07:51

I am looking for how to implement unique constraints with NULL check.

MySQL shouldn\'t allow multiple null value.

Employee:

id | name
---|---         


        
相关标签:
2条回答
  • 2020-12-16 08:20

    No, MySQL is doing the right thing, according to the SQL-99 specification.

    https://mariadb.com/kb/en/sql-99/constraint_type-unique-constraint/

    A UNIQUE Constraint makes it impossible to COMMIT any operation that would cause the unique key to contain any non-null duplicate values. (Multiple null values are allowed, since the null value is never equal to anything, even another null value.)

    If you use a UNIQUE constraint but don't want multiple rows with NULL, declare the columns as NOT NULL and prohibit any row from having NULL.

    0 讨论(0)
  • 2020-12-16 08:24

    MySQL 5.7 does allow for a workaround:

    mysql> CREATE TABLE `null_test` (
        ->   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
        ->   `const` varchar(255) NOT NULL DEFAULT '',
        ->   `deleted_at` datetime DEFAULT NULL,
        ->   PRIMARY KEY (`id`)
        -> ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
    Query OK, 0 rows affected (0.03 sec)
    

    With soft deletes, it would be nice if you could have just one row with a with a deleted_at = NULL per constraint.

    mysql> ALTER TABLE `null_test` ADD `vconst` int(1) GENERATED ALWAYS AS (((NULL = `deleted_at`) or (NULL <=> `deleted_at`))) VIRTUAL;
    Query OK, 0 rows affected (0.01 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    

    So I created a virtual column that will flip from 1 to null when deleted_at gets set.

    mysql> ALTER TABLE `null_test` ADD UNIQUE KEY `nullable_index` (`const`,`vconst`);
    Query OK, 0 rows affected (0.01 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    

    Instead of including deleted_at to the unique constraint add the virtual column, vconst.

    mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
    Query OK, 1 row affected (0.00 sec)
    
    mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
    +--------+-------+------------+--------+
    | id     | const | deleted_at | vconst |
    +--------+-------+------------+--------+
    | 999901 | Ghost | NULL       |      1 |
    +--------+-------+------------+--------+
    1 row in set (0.01 sec)
    

    No need to insert the vconst (but you cannot, anyhow).

    mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
    ERROR 1062 (23000): Duplicate entry 'Ghost-1' for key 'nullable_index'
    

    Inserting it again throws the Duplicate entry error.

    mysql> UPDATE `null_test` SET `deleted_at` = NOW() WHERE `const` = 'Ghost';
    Query OK, 1 row affected (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 0
    

    Same with setting delete_at, no need to touch vconst, it will flip automatically.

    mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
    +--------+-------+---------------------+--------+
    | id     | const | deleted_at          | vconst |
    +--------+-------+---------------------+--------+
    | 999901 | Ghost | 2017-02-16 22:07:45 |   NULL |
    +--------+-------+---------------------+--------+
    1 row in set (0.00 sec)
    
    mysql> INSERT INTO `null_test` SET `const` = 'Ghost';
    Query OK, 1 row affected (0.00 sec)
    

    Now you are free to insert a new row with the same constraints!

    mysql> SELECT * FROM `null_test` WHERE `const` = 'Ghost';
    +--------+-------+---------------------+--------+
    | id     | const | deleted_at          | vconst |
    +--------+-------+---------------------+--------+
    | 999901 | Ghost | 2017-02-16 22:07:45 |   NULL |
    | 999903 | Ghost | NULL                |      1 |
    +--------+-------+---------------------+--------+
    2 rows in set (0.01 sec)
    

    In this case, depending on how much you soft delete, setting deleted_at, you might want to include deleted_at to the index, or a new index with it, but I will let my load tests decide.

    0 讨论(0)
提交回复
热议问题