Mysql
 sql >> Database >  >> RDS >> Mysql

Imposta un vincolo univoco solo quando un campo è nullo

MySQL supporta funzionali parti chiave da 8.0.13 .

  • Se la tua versione è sufficientemente recente puoi definire il tuo indice come:

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

    (Demo su dbfiddle.uk )

    Si noti che l'indice sopra eviterà anche la duplicazione delle date per le esecuzioni completate. Se quelli dovessero essere validi, allora un indice leggermente modificato funzionerebbe:

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

    (Demo su dbfiddle.uk )

    Anche se poi inizia a sembrare un po' sporco;)

  • Se hai almeno la versione 5.7 puoi utilizzare una colonna generata (virtuale) come soluzione 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`)
    );
    

    (Demo su dbfiddle.uk )

  • Se sei bloccato su 5.6 quindi una combinazione di una colonna normale (non virtuale) e INSERT leggermente modificato le dichiarazioni funzionerebbero:

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

    In questo caso dovresti impostare is_open su true per esecuzioni incomplete e su NULL dopo il completamento, sfruttando il fatto che due NULL s sono trattati come non uguali.

    (Demo su dbfiddle.uk )