web-dev-qa-db-fra.com

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

Techniquement, NULL = NULL est False, selon cette logique, aucun NULL n'est égal à aucun NULL et tous les NULL sont distincts. Cela ne devrait-il pas impliquer que tous les NULL sont uniques et qu'un index unique devrait autoriser n'importe quel nombre de NULL?

37
user87166

Pourquoi ça marche comme ça? Parce que depuis longtemps, quelqu'un a pris une décision de conception sans savoir ou se soucier de ce que dit la norme (après tout, nous avons toutes sortes de comportements étranges avec NULLs, et 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 voulaient vraiment ce comportement particulier, autorisez-le via une option DDL comme WITH CONSIDER_NULLS_EQUAL ou WITH ALLOW_ONLY_ONE_NULL.

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

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

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

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

Cela autorise plusieurs valeurs NULL car ces lignes sont complètement exclues de la vérification des doublons. En prime, cela finirait par être un index plus petit que celui qui se composait de la table entière si plusieurs NULLs étaient autorisés (surtout quand ce n'est pas la seule colonne de l'index, il a INCLUDE colonnes, etc.). Cependant, vous souhaiterez peut-être connaître certaines des autres limitations des index filtrés:

53
Aaron Bertrand

Correct. L'implémentation d'une contrainte ou d'un index unique dans le serveur SQL autorise un et un seul NULL. Corrigez également que cela ne correspond pas techniquement à la définition de NULL, mais c'est l'une de ces choses qu'ils ont faites 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 NULLs (bien sûr).

8
Kenneth Fisher

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

Imaginez maintenant ce qui suit (où 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 viole cela, donc la base de données ne doit pas autoriser les deux éléments 2 et 4 dans le tableau en même temps.

L'approche la plus sûre et la moins flexible consisterait à 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 à se soucier de l'unicité lorsque les valeurs sont entrées.

Les programmeurs Sybase ont opté pour l'approche quelque peu sûre et peu flexible de n'autoriser qu'un seul marqueur nul dans le tableau - ce dont les commentateurs se plaignent depuis. Microsoft a continué ce comportement, je suppose pour une compatibilité ascendante.


¹ Je suis sûr d'avoir lu quelque part que Codd a envisagé d'implémenter deux marqueurs nuls - un pour inconnu, un pour inapplicable - mais l'a rejeté, mais je ne trouve pas la référence. Je me souviens bien?

P.S. Ma citation préférée à propos de 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 mauvais".

3
Greenstone Walker

Cela peut ne pas être techniquement exact, mais philosophiquement, cela m'aide à dormir la nuit ...

Comme plusieurs autres l'ont dit ou 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 y réfléchissant de cette façon, l'expression NULL == NULL devrait être évaluée à NULL, ce qui signifie inconnu.

Une contrainte Unique aurait besoin d'une valeur définitive pour la comparaison des valeurs de colonne. En d'autres termes, lors de la comparaison d'une valeur de colonne unique avec toute autre valeur de colonne à l'aide de l'opérateur d'égalité, il doit évaluer false pour être valide. L'inconnu n'est pas vraiment faux même s'il est souvent traité de falsification. Deux valeurs NULL pourraient être égales ou non ... cela ne peut tout simplement pas être définitivement déterminé.

Il est utile de considérer une contrainte unique comme des valeurs restrictives qui peuvent être déterminées comme distinctes les unes des autres. Ce que je veux dire par là, c'est que 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 avez autorisé 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.

Compte tenu de cela, je crois que, qu'il soit ou non implémenté avec précision en respectant la définition de NULL, c'est certainement beaucoup plus pratique dans la plupart des situations que d'autoriser plusieurs valeurs NULL.

2
EricJ