Pourquoi une contrainte UNIQUE n'autorise-t-elle qu'un seul NULL?


36

Techniquement, NULL = NULL est False, par cette logique, NULL n'est égal à aucun NULL et tous les NULL sont distincts. Cela ne signifie-t-il pas que tous les NULL sont uniques et qu'un index unique devrait permettre un nombre quelconque de NULL?


Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
Paul White a déclaré: GoFundMonica

Réponses:


52

Pourquoi ça marche comme ça? En effet, il y a bien longtemps, une personne avait pris une décision de conception sans savoir ni se soucier de ce que disait la norme (après tout, nous avons toutes sortes de comportements étranges avec NULLs et nous pouvons contraindre différents comportements à volonté). Cette décision a dicté que, dans ce cas NULL = NULL,.

Ce n'était pas une décision très intelligente. Ce qu’ils auraient dû faire, c’est que le comportement par défaut soit conforme à la norme ANSI, et s’ils le souhaitaient vraiment, autorisez-le via une option DDL telle que WITH CONSIDER_NULLS_EQUALou WITH ALLOW_ONLY_ONE_NULL.

Bien sûr, le recul est 20/20.

Et nous avons une solution de contournement, maintenant, de toute façon, même si ce n’est pas la plus propre ou la plus intuitive.

Vous pouvez obtenir le comportement ANSI approprié dans SQL Server 2008 et versions ultérieures en créant un index filtré unique.

CREATE UNIQUE INDEX foo ON dbo.bar(key) WHERE key IS NOT NULL;

Cela autorise plusieurs NULLvaleurs car ces lignes sont complètement exclues de la vérification des doublons. En prime, il s'agirait d'un index plus petit que celui qui comprenait la table entière si plusieurs NULLs étaient autorisés (en particulier si ce n'est pas la seule colonne de l'index, elle contient des INCLUDEcolonnes, etc.). Toutefois, vous souhaiterez peut-être connaître certaines des autres limitations des index filtrés:


8

Correct. L'implémentation d'une contrainte unique ou d'un index dans le serveur SQL autorise un et un seul NULL. Également correct, cela ne correspond techniquement pas à la définition de NULL, mais c’est une des choses qu’ils ont faite pour le rendre plus utile même s’il n’est pas "techniquement" correct. Notez qu'une PRIMARY KEY (également un index unique) n'autorise pas les valeurs NULL (bien sûr).


1
Cette technicité (SQL-Server) ne correspond pas non plus au standard SQL. Il existe un élément Connect datant de 7 ans concernant ce problème.
Ypercubeᵀᴹ

@ypercube True. C'est pourquoi j'ai dit que c'était juste l'implémentation et ne correspond pas vraiment à la définition de NULL. Je n'avais pas pensé à l'index unique filtré (même si je l'ai utilisé pour autre chose.)
Kenneth Fisher

3

Tout d’abord, arrêtez d’utiliser l’expression "valeur nulle", elle vous égarera. Au lieu de cela, utilisez l'expression "marqueur null" - un marqueur dans une colonne indiquant que la valeur réelle de cette colonne est soit manquante, soit inapplicable (mais notez que le marqueur ne dit pas laquelle de ces options est réellement le cas¹).

Imaginons maintenant ce qui suit (lorsque la base de données n’a pas une connaissance complète de la situation modélisée).

Situation          Database

ID   Code          ID   Code
--   -----         --   -----
1    A             1    A
2    B             2    (null)
3    C             3    C
4    B             4    (null)

La règle d'intégrité que nous modélisons est "le code doit être unique". La situation réelle enfreint cette règle. La base de données ne devrait donc pas autoriser la présence simultanée des éléments 2 et 4 dans la table.

L'approche la plus sûre et la moins flexible consiste à interdire les marqueurs nuls dans le champ Code. Il n'y a donc aucune possibilité de données incohérentes. L'approche la plus flexible consisterait à autoriser plusieurs marqueurs nuls et à s'inquiéter de l'unicité lorsque les valeurs sont entrées.

Les programmeurs de Sybase ont opté pour une approche relativement sûre et peu flexible, consistant à ne permettre qu'un seul marqueur null dans la table - ce dont les commentateurs se plaignent depuis. Microsoft a continué ce comportement, je suppose pour la compatibilité en amont.


¹ Je suis sûr d’avoir lu quelque part que Codd envisageait d’implémenter deux marqueurs nuls - un pour l’inconnu, un pour inapplicable - mais l’avait rejeté, mais je n’ai pas trouvé la référence. Est-ce que je me souviens correctement?

PS Ma citation préférée sur null: Louis Davidson, «Conception de base de données SQL Server 2000 professionnelle», Wrox Press, 2001, page 52. «Réduit à une seule phrase: NULL est diabolique».


1
Permettre à un célibataire nulln'atteint pas cet objectif non plus. Parce que la valeur manquante peut s'avérer identique à la valeur de l'une des autres lignes.
Martin Smith

1
Ce que @MartinSmith a dit Que faire si vous avez une contrainte de vérification CHECK (Value IN ('A','B','C','D'))? Ensuite, l'implémentation de SQL-Server et le standard SQL autorisent la table à comporter 5 lignes (une ligne pour chaque valeur plus 1 avec NULL.) Ensuite, même si la base de données est cohérente avec ses contraintes, elle ne correspond pas à l'intention du concepteur la table doit avoir un maximum de 4 lignes. Il n'y a pas de valeur dans laquelle la valeur NULL puisse être modifiée qui ne violera pas une contrainte, sauf si une ou plusieurs lignes sont supprimées.
Ypercubeᵀᴹ

1
Le fait que la norme autorise 6 voire 106 lignes au lieu de 5 ne change pas le fait qu'elles échouent toutes les deux dans ce scénario.
Ypercubeᵀᴹ

@ Martin Smith, peut-être, mais ce n'est peut-être pas le cas: le serveur de base de données ne peut pas le savoir, il ne prend donc pas ce risque et emprunte la route sécurisée. C’est ce que les programmeurs Sybase (je présume) ont décidé, ce qui a provoqué des ennuis depuis (du moins aussi loin que Inside SQL Server 6.5, le livre le plus ancien de ma bibliothèque, où Ron Soukup fait à peu près le même commentaire que Aaron Bertrand dans sa réponse). . J'imagine que cela pourrait être pire: ils n'auraient pu imposer aucun marqueur nul. :-)
Greenstone Walker

2
@ GreenstoneWalker - Il ne prend pas la route "sûre". Cela suppose que la valeur manquante ne sera pas en conflit. CREATE TABLE #T(A INT NULL UNIQUE);INSERT INTO #T VALUES (1),(NULL);UPDATE #T SET A = 1 WHERE A IS NULL;va soulever une erreur. Selon votre théorie des motivations de conception, cela aurait dû empêcher l’insertion de NULLdans le premier cas - parce que la connaissance incomplète signifie qu’il n’ya aucune garantie que la valeur soit différente.
Martin Smith

2

Cela n’est peut-être pas exact sur le plan technique, mais philosophiquement, il m’aide à dormir la nuit ...

Comme plusieurs autres l'ont dit ou y ont fait allusion, si vous pensez que NULL est inconnu, vous ne pouvez pas déterminer si une valeur NULL est en fait égale à une autre valeur NULL. En pensant de cette façon, l'expression NULL == NULL doit être évaluée à NULL, ce qui signifie inconnue.

Une contrainte unique nécessiterait une valeur définitive pour la comparaison des valeurs de colonne. En d'autres termes, lorsque vous comparez une valeur de colonne unique à une autre valeur de colonne à l'aide de l'opérateur d'égalité, elle doit être évaluée à false pour être valide. Inconnu n'est pas vraiment faux même s'il est souvent traité comme de la fausseté. Deux valeurs NULL peuvent être égales, ou non ... cela ne peut tout simplement pas être déterminé définitivement.

Il est utile de considérer une contrainte unique comme une restriction des valeurs pouvant être considérées comme distinctes les unes des autres. Ce que je veux dire par là, c'est si vous exécutez un SELECT qui ressemble à ceci:

SELECT * from dbo.table1 WHERE ColumnWithUniqueContraint="some value"

La plupart des gens s'attendent à un résultat, étant donné qu'il existe une contrainte unique. Si vous autorisiez plusieurs valeurs NULL dans ColumnWithUniqueConstraint, il serait impossible de sélectionner une seule ligne distincte de la table en utilisant NULL comme valeur comparée.

Cela étant dit, j’estime que, qu’elle soit mise en œuvre avec précision ou non, conformément à la définition de NULL, il est nettement plus pratique dans la plupart des situations que d’autoriser plusieurs valeurs NULL.


Votre sélection donnera 1 résultat, quand il y a une contrainte unique (dans n'importe quelle implémentation, pas seulement SQL-Server). Où veux-tu en venir?
ypercubeᵀᴹ

-3

L'un des principaux objectifs d'une UNIQUEcontrainte est d'empêcher les enregistrements en double. S'il est nécessaire d'avoir une table dans laquelle il peut exister plusieurs enregistrements pour lesquels une valeur est "inconnue", mais que deux enregistrements ne sont pas autorisés à avoir la même valeur "connue", les identificateurs artificiels uniques doivent être attribués aux valeurs inconnues avant d'être ajouté à la table.

Il existe quelques rares cas dans lesquels une colonne qui a une UNIQUEcontrainte et contient une seule valeur null; Par exemple, si une table contient un mappage entre les valeurs de colonne et les descriptions de texte localisées, une ligne pour NULLpermettrait de définir la description qui devrait apparaître lorsque cette colonne est présente dans une autre table NULL. Le comportement de NULLpermet ce cas d'utilisation.

Sinon, je ne vois aucune base pour une base de données avec une UNIQUEcontrainte sur une colonne pour permettre l'existence de nombreux enregistrements identiques, mais je ne vois aucun moyen d'empêcher cela tout en permettant à plusieurs enregistrements dont les valeurs de clé ne sont pas distinguables. Déclarer que ce NULLn'est pas égal à lui-même ne rendra pas les NULLvaleurs distinctes les unes des autres.


3
Les identifiants uniques artificiels sont une blague, désolé. Comment allez-vous faire cela pour un NIV? Si vous ne savez pas ce que c'est, pourquoi inventer quelque chose? Juste pour prendre de l'espace disque supplémentaire? Cela semble absurde de contourner un autre problème (par exemple, ne pas vouloir écrire l’application de manière à ce qu’elle gère normalement les valeurs NULL). Si vous avez absolument besoin de savoir pourquoi quelque chose est NULL (existe mais inconnu vs savoir qu'il n'existe pas vs ne pas savoir ou ne vous souciez pas de savoir s'il existe, par exemple), ajoutez une sorte de colonne d'état. Les jetons mènent simplement à un code qui rend le traitement difficile à gérer.
Aaron Bertrand

Beaucoup dépend du but de la contrainte d'unicité. Si un champ doit être utilisé comme identifiant, il ne doit pas être nul. Dans les cas (comme pour les NIV) où les règles commerciales suggèrent que lorsqu'un élément apparaît deux fois, l'un d'entre eux doit être erroné, mais que certains éléments peuvent être "ne sait pas", une contrainte d'unicité ne semble pas être la bonne approche. Si un véhicule a un véhicule avec un NIV connu et qu’il entre en conflit avec un autre dans la base de données, on peut savoir qu’au moins un des NIV est erroné, mais il serait préférable que la base de données rapporte la valeur estimée pour les deux enregistrements plutôt que de deviner. celui-là a raison.
Supercat

@AaronBertrand: Dans certains cas, un champ unique-si-non-null éventuellement nul aurait besoin d'être une clé de substitution ne pourrait pas être établi avant de renseigner le champ (par exemple, "ID du conjoint"), mais dans des situations telles que qu'une contrainte "unique" serait insuffisante; il serait nécessaire que si X.Spouse ne soit pas nul, X.Spouse.Spouse = X. Incidemment, quelque chose comme "conjoint" pourrait également être traité en disant que le dossier d'une personne non mariée ne devrait pas avoir "NULL" en tant que conjoint, mais plutôt sa propre pièce d'identité, auquel cas la règle X.spouse.spouse = X pourrait appliquer à tout le monde.
Supercat
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.