web-dev-qa-db-fra.com

Quel est le problème avec les colonnes Nullable dans les clés primaires composites?

Oracle n'autorise les valeurs NULL dans aucune des colonnes contenant une clé primaire. Il semble en aller de même pour la plupart des autres systèmes "de niveau entreprise".

Dans le même temps, la plupart des systèmes autorisent également des contraintes niques sur les colonnes nullables.

Pourquoi est-ce que des contraintes uniques peuvent avoir des valeurs NULL alors que les clés primaires ne le peuvent pas? Y a-t-il une raison logique fondamentale à cela, ou s'agit-il d'une limitation technique?

141
Roman Starkov

Les clés primaires servent uniquement à identifier des lignes. Ceci est fait en comparant toutes les parties d'une clé à l'entrée.

Par définition, NULL ne peut pas faire partie d'une comparaison réussie. Même une comparaison avec elle-même (NULL = NULL) va échouer. Cela signifie qu'une clé contenant NULL ne fonctionnerait pas.

De plus, NULL est autorisé dans une clé étrangère pour marquer une relation optionnelle.(*) Permettre cela aussi dans le PK casserait cela.


(*)Mise en garde: Avoir des clés étrangères nullables n'est pas une conception de base de données relationnelle propre.

S'il existe deux entités A et BA peut éventuellement être lié à B, la solution propre consiste à créer une table de résolution (disons AB). Cette table relierait A avec B: Si est une relation, elle contiendrait un enregistrement, si n'est pas alors ce ne serait pas.

203
Tomalak

Une clé primaire définit un identifiant unique pour chaque ligne dans une table: lorsqu'une table a une clé primaire, vous disposez d'un moyen sécurisé pour sélectionner n'importe quelle ligne à partir de celle-ci.

Une contrainte unique n'identifie pas nécessairement chaque ligne; il spécifie simplement que if une ligne a des valeurs dans ses colonnes, alors elles doivent être uniques. Ce n'est pas suffisant pour identifier de manière unique la ligne chaque, ce que doit faire une clé primaire.

56
Tony Andrews

Fondamentalement, rien n’est faux avec une valeur NULL dans une clé primaire à plusieurs colonnes. Toutefois, le concepteur n’en a probablement pas l’intention, ce qui explique pourquoi de nombreux systèmes émettent une erreur lorsque vous essayez ceci.

Considérons le cas des versions de module/package stockées sous la forme d'une série de champs:

CREATE TABLE module
  (name        varchar(20) PRIMARY KEY,
   description text DEFAULT '' NOT NULL);

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20),
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

Les 5 premiers éléments de la clé primaire sont des parties régulièrement définies d'une version, mais certains paquets ont une extension personnalisée qui n'est généralement pas un entier (comme "rc-foo" ou "Vanilla" ou "beta" ou autre chose pour quelqu'un qui quatre champs est insuffisant pourrait rêver). Si un paquet n'a pas d'extension, alors il a la valeur NULL dans le modèle ci-dessus et aucun dommage ne serait causé en laissant les choses de cette façon.

Mais que est un NULL? Il est supposé représenter un manque d'information, un inconnu. Cela dit, cela a peut-être plus de sens:

CREATE TABLE version
  (module      varchar(20) REFERENCES module,
   major       integer NOT NULL,
   minor       integer DEFAULT 0 NOT NULL,
   patch       integer DEFAULT 0 NOT NULL,
   release     integer DEFAULT 1 NOT NULL,
   ext         varchar(20) DEFAULT '' NOT NULL,
   notes       text DEFAULT '' NOT NULL,
   PRIMARY KEY (module, major, minor, patch, release, ext));

Dans cette version, la partie "ext" du tuple n'est pas NULL mais utilise par défaut une chaîne vide - qui est sémantiquement (et pratiquement) différente d'un NULL. Un NULL est un inconnu, alors qu'une chaîne vide est un enregistrement délibéré de "quelque chose qui n'est pas présent". En d'autres termes, "vide" et "null" sont des choses différentes. C'est la différence entre "Je n'ai pas de valeur ici" et "Je ne sais pas quelle est la valeur ici".

Lorsque vous enregistrez un paquet ne disposant pas d'une extension de version, vous savez il ne possède pas d'extension. Une chaîne vide est donc la valeur correcte. Un NULL ne serait correct que si vous ne saviez pas s'il avait une extension ou non, ou si vous saviez que c'était le cas mais que vous ne saviez pas ce que c'était. Cette situation est plus facile à gérer dans les systèmes où les valeurs de chaîne sont la norme, car il n’existe aucun moyen de représenter un "entier vide" autre que l’insertion de 0 ou 1, qui finira par être cumulé dans les comparaisons effectuées ultérieurement ( ses propres implications) *.

Incidemment, les deux méthodes sont valables dans Postgres (étant donné que nous discutons des RDMBS "d'entreprise"), mais les résultats de la comparaison peuvent varier un peu lorsque vous ajoutez une valeur NULL à la combinaison - car NULL == "ne sait pas". les résultats d'une comparaison impliquant une valeur nulle, NULL étant donné que vous ne pouvez pas savoir quelque chose d'inconnu. DANGER! Réfléchissez bien à cela: cela signifie que la comparaison a pour résultat NULL propage à travers une série de comparaisons. Cela peut être une source de bugs subtils lors du tri, de la comparaison, etc.

Postgres suppose que vous êtes un adulte et peut prendre cette décision vous-même. Oracle et DB2 supposent que vous n'avez pas réalisé que vous faisiez une bêtise et génèrent une erreur. Ceci est généralement la bonne chose, mais pas toujours - vous pourriez en fait ne pas connaître et avoir une valeur NULL dans certains cas, laissant ainsi une ligne avec un inconnu. L'élément par rapport auquel des comparaisons significatives sont impossibles est le comportement correct.

Dans tous les cas, vous devez vous efforcer d’éliminer le nombre de champs NULL que vous autorisez dans l’ensemble du schéma et encore plus lorsque vous utilisez des champs faisant partie d’une clé primaire. Dans la grande majorité des cas, la présence de colonnes NULL est une indication de la conception de schéma non normalisée (par opposition à une délibérément dé-normalisée) et doit être considérée avec la plus grande attention avant d'être acceptée.

[* REMARQUE: Il est possible de créer un type personnalisé qui est l'union d'entiers et un type "en bas" qui signifierait sémantiquement "vide" par opposition à "inconnu". Malheureusement, ceci introduit un peu de complexité dans les opérations de comparaison et être en général digne de ce nom ne vaut pas la peine d'être fait dans la pratique, car vous ne devriez pas être autorisé à utiliser beaucoup de NULL valeurs du tout. Cela dit, il serait merveilleux que les SGBDR incluent un type par défaut BOTTOM en plus de NULL afin d'éviter l'habitude de confondre la sémantique de "aucune valeur" avec "valeur inconnue".]

44
zxq9

NULL == NULL -> false (au moins dans les SGBD)

Par conséquent, vous ne pourrez pas récupérer de relation utilisant une valeur NULL, même avec des colonnes supplémentaires contenant des valeurs réelles.

19
Cogsy

La réponse de Tony Andrews est décente. Mais la vraie réponse est que ceci a été une convention utilisée par la communauté de base de données relationnelle et n'est PAS une nécessité. Peut-être que c'est une bonne convention, peut-être pas.

En comparant quoi que ce soit avec NULL, on obtient UNKNOWN (3ème valeur de vérité). Ainsi, comme cela a été suggéré avec les valeurs nulles, toute la sagesse traditionnelle concernant l’égalité est mise à l’écart. Eh bien, c'est comme ça à première vue.

Mais je ne pense pas que ce soit nécessairement le cas et même les bases de données SQL ne pensent pas que NULL détruit toutes les possibilités de comparaison.

Exécutez dans votre base de données la requête SELECT * FROM VALUES (NULL) UNION SELECT * FROM VALUES (NULL)

Ce que vous voyez n'est qu'un tuple avec un attribut ayant la valeur NULL. Donc, l'union a reconnu ici les deux valeurs NULL égales.

Lors de la comparaison d'une clé composite à 3 composants avec un tuple à 3 attributs (1, 3, NULL) = (1, 3, NULL) <=> 1 = 1 AND 3 = 3 AND NULL = NULL Le résultat obtenu est UNKNOWN .

Mais nous pourrions définir un nouveau type d'opérateur de comparaison, par exemple. ==. X == Y <=> X = Y OR (X IS NULL ET Y IS NULL)

Avoir ce type d'opérateur d'égalité rendrait les clés composites avec des composants nuls ou les clés non composites avec une valeur null non problématiques.

4
Rami Ojares

Je crois toujours que c'est un défaut fondamental/fonctionnel provoqué par une technicité. Si vous avez un champ facultatif permettant d'identifier un client, vous devez maintenant y insérer une valeur factice, car NULL! = NULL n'est pas particulièrement élégant, mais il s'agit d'un "standard de l'industrie".

0
Adriaan Davel