Mysql
 sql >> Base de données >  >> RDS >> Mysql

Définir une contrainte d'unicité uniquement lorsqu'un champ est nul

MySQL prend en charge fonctionnel éléments clés depuis 8.0.13 .

  • Si votre version est suffisamment récente, vous pouvez définir votre index comme :

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

    (Démo sur dbfiddle.uk )

    Notez que l'index ci-dessus empêchera également les dates en double pour les exécutions terminées. Si ceux-ci devaient être valides, un index légèrement modifié fonctionnerait :

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

    (Démo sur dbfiddle.uk )

    Même si ça commence à être un peu sale;)

  • Si vous avez au moins la version 5.7 vous pouvez utiliser une colonne générée (virtuelle) comme solution de contournement :

    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`)
    );
    

    (Démo sur dbfiddle.uk )

  • Si vous êtes bloqué sur 5.6 puis une combinaison d'une colonne normale (non virtuelle) et de INSERT légèrement modifié les instructions fonctionneraient :

    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`)
    );
    

    Dans ce cas, vous définiriez is_open à true pour les exécutions incomplètes et à NULL après l'achèvement, en utilisant le fait que deux NULL s sont traités comme non égaux.

    (Démo sur dbfiddle.uk )