web-dev-qa-db-fra.com

mysql - index unique avec null - solution réelle n'importe qui?

CREATE TABLE IF NOT EXISTS b2c_constants (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(64) NOT NULL,
    is_deleted BOOL DEFAULT FALSE,
    UNIQUE (name)
) ENGINE InnoDB CHARSET utf8 COLLATE utf8_unicode_ci

CREATE TABLE IF NOT EXISTS b2c_constant_bindings (
    constant_id INT UNSIGNED NOT NULL,
    company_id INT UNSIGNED NOT NULL,
    object_id INT UNSIGNED DEFAULT NULL,
    property_id INT UNSIGNED DEFAULT NULL,
    value VARCHAR(255) NOT NULL,
    UNIQUE (constant_id, company_id, object_id, property_id),
    FOREIGN KEY (constant_id) REFERENCES b2c_constants (id) ON UPDATE RESTRICT ON DELETE CASCADE,
    FOREIGN KEY (company_id) REFERENCES companies (id) ON UPDATE RESTRICT ON DELETE CASCADE,
    FOREIGN KEY (object_id) REFERENCES b2b_objects (id) ON UPDATE RESTRICT ON DELETE CASCADE,
    FOREIGN KEY (property_id) REFERENCES b2b_properties (id) ON UPDATE RESTRICT ON DELETE CASCADE
) ENGINE InnoDB CHARSET utf8 COLLATE utf8_unicode_ci

Le problème vient de la clé unique dans la table bindings. Si j'ai des données comme:

constant_id   company_id   object_id   property_id   value
1             1            null        null          foo
1             1            1           null          bar
1             1            1           1             baz

Vous pouvez dupliquer les deux premières lignes sans erreur à l'infini, ce qui n'est évidemment pas souhaitable.

L'idée avec cette structure est de permettre de lier des constantes globalement par entreprise, par objet en entreprise et par propriété en entreprise, mais de n'autoriser que des constantes uniques pour chacune d'elles.

Existe-t-il un moyen de résoudre ce problème dans la base de données tout en conservant les clés étrangères et une structure de table simple? Je sais que je pourrais faire type ENUM ('company', 'object', 'property'), type_id INT, mais avec cela je perds les clés étrangères ainsi que l'ID de société requis pour toutes les constantes.

7
jurchiks

[Ceci n'est pas testé car il nécessite MySQL 5.7.6 ou supérieur]

Bien que je sois d'accord avec les commentaires ci-dessus, j'ai encore une idée qui peut être essayée, qui je pense n'est pas la meilleure en termes de performances, mais cela résout le problème que vous décrivez.

L'idée est d'ajouter une clé qui traite de NULL comme une valeur concrète, comme '0', ou toute autre valeur. Ensuite, indexez de manière unique la combinaison des champs que vous souhaitez rendre unique.

MySQL 5.7.6 prend en charge colonnes générées .

ALTER TABLE b2c_constant_bindings
ADD unique_md5 char(32) AS 
    (MD5(CONCAT_WS('X', ifnull(constant_id, 0), ifnull(company_id, 0), ifnull(object_id, 0), ifnull(property_id,0)))) 
    UNIQUE;

Au lieu d'utiliser CONCAT, j'utilise CONCAT_WS Pour éviter d'avoir les mêmes résultats dans des cas similaires à CONCAT(1, 23) et CONCAT(12, 3) (ss per @Rick James ' remarquer).

8
Jehad Keriaki