Suite à ma question "Pourquoi utiliser‘ pas une clé primaire null ’dans TSQL?" ...
D'après ce que j'ai compris d'autres discussions, certains SGBDR (par exemple, SQLite, MySQL) autorise "unique" NULL dans la clé primaire.
Pourquoi est-ce permis et comment pourrait-il être utile?
Contexte: Je pense qu'il est bénéfique pour la communication avec des collègues et des professionnels de bases de données de connaître les différences entre les concepts fondamentaux, les approches et leurs implémentations dans différents SGBD.
Afin de déterminer le caractère unique des valeurs de clé primaire, les valeurs NULL sont considérées comme distinctes de toutes les autres valeurs, y compris les autres NULL.
Si une instruction INSERT ou UPDATE tente de modifier le contenu de la table de sorte que deux lignes ou plus présentent des valeurs de clé primaire identiques, il s'agit d'une violation de contrainte. Selon le standard SQL, PRIMARY KEY devrait toujours impliquer NOT NULL. Malheureusement, en raison d'une supervision de longue date du codage, ce n'est pas le cas dans SQLite.
Sauf si la colonne est une INTEGER PRIMARY KEY SQLite autorise les valeurs NULL dans une colonne PRIMARY KEY . Nous pourrions modifier SQLite pour le rendre conforme à la norme (et nous le ferons peut-être à l'avenir), mais au moment où la surveillance a été découverte, SQLite était tellement utilisé que nous craignions de casser le code hérité si nous réglions le problème.
Nous avons donc choisi de continuer à autoriser les valeurs NULL dans les colonnes PRIMARY KEY. Les développeurs doivent toutefois savoir que nous pouvons modifier SQLite pour le rendre conforme à la norme SQL à l'avenir et concevoir de nouveaux programmes en conséquence.
Supposons que vous ayez une clé primaire contenant une colonne nullable Kn.
Si vous souhaitez qu'une seconde ligne soit rejetée pour le motif que, dans cette seconde ligne, Kn est null et que la table contient déjà une ligne avec Kn null, vous demandez en fait que le système traite la comparaison "row1.Kn = row2 .Kn "comme donnant TRUE (parce que vous voulez d'une manière ou d'une autre que le système détecte que les valeurs de clé dans ces lignes sont bien égales). Cependant, cette comparaison se résume à la comparaison "null = null", et le standard spécifie déjà explicitement que null ne se compare à rien, y compris lui-même.
Autoriser ce que vous voulez équivaudrait donc à une modification de SQL par rapport à ses propres principes concernant le traitement de null. Il existe d'innombrables incohérences dans SQL, mais celui-ci en particulier n'a jamais été dépassé par le comité.
Je ne sais pas si les anciennes versions de MySQL diffèrent sur ce point, mais à partir des versions modernes, une clé primaire doit se trouver sur des colonnes non nulles. Voir la page de manuel sur CREATE TABLE
: "Un PRIMARY KEY
est un index unique dans lequel toutes les colonnes de clé doivent être définies en tant que NOT NULL
. Si elles ne sont pas explicitement déclarées en tant que NOT NULL
, MySQL les déclare de manière implicite (et silencieuse)."
En ce qui concerne la théorie des bases de données relationnelles:
Selon les données que vous modélisez, une valeur "composée" peut être utilisée à la place de NULL. J'ai utilisé 0, "N/A", "1er janvier 1980" et des valeurs similaires pour représenter des données factices "connues comme manquantes".
La plupart des moteurs de base de données, sinon tous, autorisent une contrainte ou un index UNIQUE, ce qui autorise les valeurs de colonne NULL, bien que (idéalement) une seule ligne puisse recevoir la valeur null (sinon, il ne s'agirait pas d'une valeur unique). Ceci peut être utilisé pour supporter les situations irritantes pragmatiques (mais parfois nécessaires) qui ne rentrent pas parfaitement dans la théorie relationnelle.
Cela pourrait vous permettre d’implémenter le Null Object Pattern de manière native dans la base de données. Ainsi, si vous utilisiez quelque chose de similaire dans le code, qui interagissait de manière très étroite avec la base de données, vous pouviez simplement rechercher l'objet correspondant à la clé sans avoir à effectuer une vérification nulle.
Maintenant, je ne suis pas sûr que cela soit utile, mais la question est de savoir si les avantages de refuser les clés nulles l'emportent, dans tous les cas, sur les inconvénients d'entraver quelqu'un qui (pour le meilleur ou pour le pire) veut réellement utiliser des clés nulles. Cela ne vaut la peine que si vous pouviez démontrer que certaines améliorations non triviales (telles qu'une recherche de clé plus rapide) permettaient de garantir que les clés sont non nulles. Certains moteurs de base de données montreraient cela, d'autres non. Et s’il n’ya pas de véritables avantages de forcing this, pourquoi limiter artificiellement vos clients?
Comme indiqué dans d'autres réponses, NULL devait signifier "les informations devant figurer dans cette colonne sont inconnues". Cependant, il est fréquemment utilisé aussi pour indiquer une autre signification de "cet attribut n'existe pas". Cette interprétation est particulièrement utile lorsque vous examinez les champs d'horodatage qui sont interprétés comme l'heure à laquelle un événement particulier s'est produit. Dans ce cas, la valeur NULL est souvent utilisée pour indiquer que l'événement ne s'est pas encore produit.
Le fait que SQL ne supporte pas très bien cette interprétation est un problème - pour que cela fonctionne correctement, il doit vraiment avoir une valeur distincte (quelque chose comme "jamais") qui ne se comporte pas comme un nul ("jamais" devrait égal à "jamais" et doit être comparé comme étant supérieur à toutes les autres valeurs). Mais comme SQL n’a pas cette notion, et qu’il n’ya pas de moyen pratique de l’ajouter, utiliser null à cette fin est souvent le meilleur choix.
Cela pose le problème que lorsqu'une horodatage d'un événement qui pourrait ne pas avoir eu lieu doit faire partie de la clé primaire d'une table (une exigence courante est peut-être l'utilisation d'une clé naturelle avec un horodatage de suppression lors de l'utilisation d'une suppression logicielle avec une exigence pour la possibilité de recréer l'élément après la suppression), vous voulez vraiment que la clé primaire ait une colonne nullable. Hélas, cela n'est pas autorisé dans la plupart des bases de données et vous devez utiliser une clé primaire artificielle (par exemple, un numéro de séquence de lignes) et une contrainte UNIQUE pour ce qui aurait autrement dû être votre clé primaire réelle.
Un exemple de scénario, afin de clarifier ceci: j'ai une table users
. Comme je demande à chaque utilisateur d'avoir un nom d'utilisateur distinct, je décide d'utiliser username
comme clé primaire. Je souhaite prendre en charge la suppression des utilisateurs, mais comme je dois suivre l'historique des utilisateurs à des fins d'audit, j'utilise la suppression logicielle (dans la première version du schéma, j'ajoute un indicateur "supprimé" à l'utilisateur et je m'assure que la suppression a bien lieu. flag est coché dans toutes les requêtes où seuls les utilisateurs actifs sont attendus).
Une exigence supplémentaire, cependant, est que si un nom d'utilisateur est supprimé, il doit être disponible pour que les nouveaux utilisateurs puissent s'enregistrer. Un moyen intéressant d'y parvenir serait de faire passer l'indicateur supprimé à un horodatage Nullable (où les valeurs NULL indiquent que l'utilisateur n'a pas été supprimé) et de l'insérer dans la clé primaire. Si les clés primaires autorisaient les colonnes Nullable, cela aurait l'effet suivant:
deleted
de cet utilisateur est nulle serait refusée en tant qu'entrée de clé en doubledeleted
soit un horodatage pour le moment où la suppression a eu lieu.deleted
) peut être créé avec succès.Cependant, cela ne peut pas être réalisé avec le SQL standard. Vous devez donc utiliser une clé primaire différente (probablement un ID utilisateur généré dans ce cas) et utiliser une contrainte UNIQUE pour imposer l'unicité de (username
, deleted
).
Avoir la clé primaire nulle peut être bénéfique dans certains scénarios. Dans l'un de mes projets, j'ai utilisé cette fonctionnalité lors de la synchronisation de bases de données: une sur le serveur et plusieurs sur des périphériques d'utilisateurs différents. Compte tenu du fait que tous les utilisateurs n’ont pas toujours accès à Internet, j’ai décidé que seule la base de données principale serait en mesure de donner des identifiants à mes entités. SQLite a son propre mécanisme de numérotation des lignes. Si j'avais utilisé un champ d'identifiant supplémentaire, j'utiliserais davantage de bande passante. Avoir la valeur null en tant qu'id me notifie non seulement qu'une entité a été créée sur le périphérique client alors qu'il n'avait pas accès à Internet, mais diminue également la complexité du code. Le seul inconvénient est que sur le périphérique client, je ne peux pas obtenir une entité par son ID si elle n'a pas déjà été synchronisée avec la base de données principale. Cependant, ce n'est pas un problème puisque mon utilisateur se soucie des entités pour leurs paramètres, pas de leur identifiant unique.