仅当字段为空时设置唯一约束

Set a unique constraint only when a field is null

我有这个table:

CREATE TABLE `executed_tests` (
    `id` INTEGER AUTO_INCREMENT NOT NULL,
    `user_id` INTEGER NOT NULL,
    `test_id` INTEGER NOT NULL,
    `start_date` DATE NOT NULL,
    `completed_date` DATE,
    PRIMARY KEY (`id`)
);

我想在字段 user_idtest_id 上设置唯一约束,但仅当 conclusion_date 为空时。如果 conclusion_date 不为空,则约束不适用。

因此每个用户和测试只存在一次不完整的执行。

像这样:

UNIQUE(`user_id`, `test_id`) WHEN (`completed_date` IS NULL)

如何在 MySQL 5.6 上完成此操作?

MySQL 支持 functional key parts8.0.13.

  • 如果您的版本足够新,您可以将索引定义为:

    UNIQUE(`user_id`, `test_id`, (IFNULL(`completed_date`, -1)))
    

    (Demo on dbfiddle.uk)

    请注意,上述索引还将防止已完成执行的日期重复。如果这些应该是有效的,那么稍微修改一下索引就可以了:

    UNIQUE(`user_id`, `test_id`, (
        CASE WHEN `completed_date` IS NOT NULL
        THEN NULL
        ELSE 0
    END))
    

    (Demo on dbfiddle.uk)

    虽然后来开始觉得有点脏了;)

  • 如果您的版本至少为 5.7,您可以使用(虚拟)generated column 作为解决方法:

    CREATE TABLE `executed_tests` (
        `id` INTEGER AUTO_INCREMENT NOT NULL,
        `user_id` INTEGER NOT NULL,
        `test_id` INTEGER NOT NULL,
        `start_date` DATE NOT NULL,
        `completed_date` DATE,
        `_helper` CHAR(11) AS (IFNULL(`completed_date`, -1)),
        PRIMARY KEY (`id`),
        UNIQUE(`user_id`, `test_id`, `_helper`)
    );
    

    (Demo on dbfiddle.uk)

  • 如果您卡在 5.6 上,那么结合使用常规 (non-virtual) 列和稍作修改的 INSERT 语句会工作:

    CREATE TABLE `executed_tests` (
        `id` INTEGER AUTO_INCREMENT NOT NULL,
        `user_id` INTEGER NOT NULL,
        `test_id` INTEGER NOT NULL,
        `start_date` DATE NOT NULL,
        `completed_date` DATE,
        `is_open` BOOLEAN,
        PRIMARY KEY (`id`),
        UNIQUE(`user_id`, `test_id`, `is_open`)
    );
    

    在这种情况下,您可以将 is_open 设置为 true 用于不完整的执行,并在完成后设置为 NULL,利用两个 NULL 被处理的事实不等于。

    (Demo on dbfiddle.uk)