Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Defina uma restrição exclusiva somente quando um campo for nulo


MySQL suporta funcional peças-chave desde 8.0.13 .

  • Se sua versão for suficientemente recente, você pode definir seu índice como:
    UNIQUE(`user_id`, `test_id`, (IFNULL(`completed_date`, -1)))
    

    (Demonstração em dbfiddle.uk )

    Observe que o índice acima também evitará datas duplicadas para execuções concluídas. Se isso for válido, um índice ligeiramente modificado funcionaria:
    UNIQUE(`user_id`, `test_id`, (
        CASE WHEN `completed_date` IS NOT NULL
        THEN NULL
        ELSE 0
    END))
    

    (Demonstração em dbfiddle.uk )

    Embora então comece a parecer um pouco sujo;)

  • Se você tiver pelo menos a versão 5.7 você pode usar uma coluna (virtual) como solução alternativa:
    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`)
    );
    

    (Demonstração em dbfiddle.uk )

  • Se você está preso em 5.6 em seguida, uma combinação de uma coluna regular (não virtual) e INSERT ligeiramente modificada declarações funcionariam:
    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`)
    );
    

    Neste caso, você definiria is_open para true para execuções incompletas e para NULL após a conclusão, fazendo uso do fato de que dois NULL s são tratados como não iguais.

    (Demonstração em dbfiddle.uk )