web-dev-qa-db-fra.com

Clé unique avec NULL

Cette question nécessite des antécédents hypothétiques. Considérons une table employee avec les colonnes name, date_of_birth, title, salary, en utilisant MySQL comme SGBDR. Puisque si une personne a le même nom et la même date de naissance qu’une autre personne, il s’agit par définition de la même personne (à l’exception de coïncidences surprenantes où deux personnes nommées Abraham Lincoln sont nées le 12 février 1809), clé unique sur name et date_of_birth qui signifie "ne stockez pas la même personne deux fois." Considérons maintenant ces données:

id name        date_of_birth title          salary
 1 John Smith  1960-10-02    President      500,000
 2 Jane Doe    1982-05-05    Accountant      80,000
 3 Jim Johnson NULL          Office Manager  40,000
 4 Tim Smith   1899-04-11    Janitor         95,000

Si je tente maintenant d'exécuter l'instruction suivante, elle devrait échouer et échouera:

INSERT INTO employee (name, date_of_birth, title, salary)
VALUES ('Tim Smith', '1899-04-11', 'Janitor', '95,000')

Si j'essaye celui-ci, ça va réussir:

INSERT INTO employee (name, title, salary)
VALUES ('Jim Johnson', 'Office Manager', '40,000')

Et maintenant, mes données vont ressembler à ceci:

id name        date_of_birth title          salary
 1 John Smith  1960-10-02    President      500,000
 2 Jane Doe    1982-05-05    Accountant      80,000
 3 Jim Johnson NULL          Office Manager  40,000
 4 Tim Smith   1899-04-11    Janitor         95,000
 5 Jim Johnson NULL          Office Manager  40,000

Ce n'est pas ce que je veux mais je ne peux pas dire que je suis entièrement en désaccord avec ce qui s'est passé. Si nous parlons en termes d'ensembles mathématiques,

{'Tim Smith', '1899-04-11'} = {'Tim Smith', '1899-04-11'} <-- TRUE
{'Tim Smith', '1899-04-11'} = {'Jane Doe', '1982-05-05'} <-- FALSE
{'Tim Smith', '1899-04-11'} = {'Jim Johnson', NULL} <-- UNKNOWN
{'Jim Johnson', NULL} = {'Jim Johnson', NULL} <-- UNKNOWN

Je suppose que MySQL dit: "Puisque je ne sais pas que Jim Johnson avec une date de naissance NULL ne figure pas déjà dans ce tableau, je vais l'ajouter."

Ma question est: Comment puis-je éviter les doublons même si date_of_birth n'est pas toujours connu? Le mieux que j'ai trouvé jusqu'ici est de déplacer date_of_birth dans une autre table. Le problème avec cela, cependant, est que je pourrais me retrouver avec, par exemple, deux caissiers avec le même nom, le même titre et le même salaire, des dates de naissance différentes et aucun moyen de les stocker tous les deux sans avoir des doublons.

35
Jason Swett

Une propriété fondamentale d'une unique clé est queit doit être unique. Faire partie de cette clé Nullable détruit cette propriété.

Il y a deux solutions possibles à votre problème:

  • Une façon, la mauvaise, serait d'utiliser une date magique pour représenter l'inconnu. Cela vous permet de dépasser Le "problème" du SGBD, mais ne résout pas le problème dans un sens logique . Attendez-vous à des problèmes avec deux entrées "John Smith" ayant une date inconnue. S'agit-il de personnes identiques ou uniques?? Si vous savez qu'ils sont différents, vous revenez au même vieux problème -- Votre clé unique n'est tout simplement pas unique. Ne pensez même pas à assigner toute une gamme de dates magiques. Pour représenter «inconnu», c’est vraiment la route de l’enfer.

  • Un meilleur moyen consiste à créer un attribut EmployeeId en tant que clé de substitution. Ceci est juste un identifiant Arbitraire que vous attribuez à des individus que vous connaissez sont uniques. Cet identifiant .__ est souvent juste une valeur entière . Créez ensuite une table Employee pour associer EmployeeId (clé Unique et non nullable) à ce que vous pensez être les attributaires dépendants, dans ce cas. Nom et date de naissance (chacun pouvant être annulé). Utilisez la clé de substitution EmployeeId partout où vous avez précédemment utilisé le nom/la date de naissance. Cela ajoute une nouvelle table à votre système mais Résout le problème des valeurs inconnues de manière robuste.

22
NealB

Je pense que MySQL le fait ici. Certaines autres bases de données (par exemple Microsoft SQL Server) considèrent NULL comme une valeur qui ne peut être insérée qu'une seule fois dans une colonne UNIQUE, mais personnellement, je trouve ce comportement étrange et inattendu.

Cependant, puisque c'est ce que vous voulez, vous pouvez utiliser une valeur "magique" au lieu de NULL, telle qu'une date très ancienne.

6
Mark Byers

Votre problème de ne pas avoir de doublons basés sur le nom ne peut pas être résolu car vous n’avez pas de clé naturelle. Mettre une fausse date pour des personnes dont la date de naissance est inconnue ne résoudra pas votre problème. John Smith, né en 1900/01/01, sera toujours une personne différente de John Smithh, né en 1960/03/09.

Je travaille chaque jour avec des données de noms provenant de grandes et de petites entreprises et je peux vous assurer qu'elles ont tout le temps deux personnes différentes portant le même nom. Parfois avec le même titre. La date de naissance n'est pas une garantie d'unicité non plus, beaucoup de John Smith sont nés à la même date. Heck quand nous travaillons avec des médecins de données de bureau nous avons souvent deux médecins avec le même nom, adresse et numéro de téléphone (combinaisons père et fils)

Votre meilleur choix est d'avoir un ID d'employé si vous insérez des données d'employé pour identifier chaque employé de manière unique. Ensuite, recherchez le nom unique dans l'interface utilisateur et, s'il y a une ou plusieurs correspondances, demandez à l'utilisateur s'il le pensait bien et s'il répond non, insérez l'enregistrement. Ensuite, créez un processus de correction pour résoudre les problèmes si deux identifiants sont attribués par accident. 

5
HLGEM

Il y a une autre façon de le faire. Ajout d'une colonne (non nullable) pour représenter la valeur String de la colonne date_of_birth. La nouvelle valeur de colonne serait "" (chaîne vide) si date_of_birth est null.

Nous nommons la colonne sous la forme date_of_birth_str et créons un employé de contrainte unique (name, date_of_birth_str). Ainsi, lorsque deux personnes recensées ont le même nom et la même valeur null date_of_birth, la contrainte unique fonctionne toujours.

Mais les efforts de maintenance pour les deux colonnes de même signification et les dommages de performance de la nouvelle colonne doivent être examinés avec soin.

3
Mike Lue

Je recommande de créer une colonne de table supplémentaire checksum qui contiendra le hachage md5 de name et date_of_birth. Supprimer la clé unique (name, date_of_birth) car elle ne résout pas le problème. Créez une clé unique sur la somme de contrôle.

ALTER TABLE employee 
    ADD COLUMN checksum CHAR(32) NOT NULL;

UPDATE employee 
SET checksum = MD5(CONCAT(name, IFNULL(date_of_birth, '')));

ALTER TABLE employee 
    ADD UNIQUE (checksum);

Cette solution crée un léger surcoût technique, car pour chaque paire insérée, vous devez générer un hachage (la même chose pour toutes les requêtes de recherche). Pour des améliorations supplémentaires, vous pouvez ajouter un déclencheur qui générera un hachage pour vous dans chaque insertion:

CREATE TRIGGER before_insert_employee 
BEFORE INSERT ON employee
FOR EACH ROW
    IF new.checksum IS NULL THEN
      SET new.checksum = MD5(CONCAT(new.name, IFNULL(new.date_of_birth, '')));
    END IF;
1
Alexander Yancharuk

J'ai eu un problème similaire à celui-ci, mais avec une torsion. Dans votre cas, chaque employé a son anniversaire, même s'il est inconnu. Dans ce cas, il est logique que le système attribue deux valeurs aux employés avec des dates de naissance inconnues mais avec des informations identiques. La réponse acceptée par NealB est très précise.

Cependant, le problème que j'ai rencontré était un problème dans lequel le champ de données n'avait pas nécessairement de valeur. Par exemple, si vous ajoutez un champ 'name_of_spouse' à votre table, il n'y aura pas nécessairement de valeur pour chaque ligne de la table. Dans ce cas, le premier point de NealB (la "mauvaise façon") est logique. Dans ce cas, une chaîne 'Aucune' doit être insérée dans la colonne name_of_spouse pour chaque ligne dans laquelle il n'y avait pas de conjoint connu. 

La situation où j'ai rencontré ce problème était en écrivant un programme avec base de données pour classifier le trafic IP. L'objectif était de créer un graphique du trafic IP sur un réseau privé. Chaque paquet a été placé dans une table de base de données avec un index de connexion unique basé sur ses sources ip et dest, port source et dest, protocole de transport et protocole d'application. Cependant, beaucoup de paquets n'ont tout simplement pas de protocole d'application. Par exemple, tous les paquets TCP sans protocole d'application doivent être classés ensemble et occuper une entrée unique dans l'index des connexions. C'est parce que je veux que ces paquets forment un seul bord de mon graphe. Dans cette situation, j’ai suivi mes propres conseils et a stocké une chaîne "Aucune" dans le champ du protocole d’application pour garantir que ces paquets forment un groupe unique.

0
kingledion

La solution idéale consisterait à prendre en charge les systèmes britanniques basés sur les fonctions, mais cela devient plus complexe car mySQL devrait également prendre en charge les index basés sur les fonctions. Cela éviterait d'avoir à utiliser des "fausses" valeurs à la place de NULL, tout en laissant aux développeurs la possibilité de décider comment traiter les valeurs NULL au Royaume-Uni. Malheureusement, mySQL ne prend pas en charge les fonctionnalités dont je suis conscient, nous avons donc des solutions de contournement.

CREATE TABLE employee( 
 name CHAR(50) NOT NULL, 
 date_of_birth DATE, 
 title CHAR(50), 
 UNIQUE KEY idx_name_dob (name, IFNULL(date_of_birth,'0000-00-00 00:00:00'))
);

(Notez l'utilisation de la fonction IFNULL () dans la définition de clé unique)

0
Paul