Comment créer un index unique sur une colonne NULL?

101

J'utilise SQL Server 2005. Je veux contraindre les valeurs d'une colonne à être uniques, tout en autorisant NULLS.

Ma solution actuelle implique un index unique sur une vue comme celle-ci:

CREATE VIEW vw_unq WITH SCHEMABINDING AS
    SELECT Column1
      FROM MyTable
     WHERE Column1 IS NOT NULL

CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)

Des meilleures idées?

Nuno G
la source
16
aucune chance d'utiliser SQL 2008? vous pouvez créer un index filtré en utilisant 'where'
Simon_Weaver
3
Vous ne vouliez pas dire unique, autorisant les NULL , vous semblez avoir voulu dire unique, mais incluant plusieurs NULL . Sinon, NULL est indexé comme toute autre valeur et la contrainte d'unicité fonctionne comme prévu - mais pas selon les normes SQL, comme @pst mentionné dans un commentaire ci-dessous.
Suncat2000

Réponses:

26

À peu près sûr que vous ne pouvez pas faire cela, car cela viole le but des uniques.

Cependant, cette personne semble avoir un travail décent: http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html

willasay what
la source
2
Il semble que le contenu du lien que vous avez fourni ait été (partiellement) copié sans attribution d'ici: decipherinfosys.wordpress.com/2007/11/30/...
Tom Juergens
77
Je ne suis pas d'accord pour dire que cela «viole le but des uniques» - NULL est une valeur spéciale dans SQL (similaire à bien des égards à NaN) et doit être traitée en conséquence. C'est en fait un échec dans SQL Server pour honorer diverses spécifications SQL: voici un lien pour une demande de "mise en œuvre correcte" pour ce qu'elle vaut: connect.microsoft.com/SQLServer/feedback/details/299229/… .
5
pour référence en 2008, vous pouvez faire CREATE UNIQUE INDEX foo ON dbo.bar (key) WHERE key IS NOT NULL;
niico
2
Je suis également en désaccord avec "viole le but des uniques", NULL n'est pas égal à NULL, vous devriez donc pouvoir créer un index unique sur une colonne Nullable et insérer plusieurs NULL.
Wodzu
105

À l'aide de SQL Server 2008, vous pouvez créer un index filtré: http://msdn.microsoft.com/en-us/library/cc280372.aspx . (Je vois que Simon a ajouté cela comme un commentaire, mais j'ai pensé que cela méritait sa propre réponse car le commentaire est facilement manqué.)

Une autre option est un déclencheur pour vérifier l'unicité, mais cela pourrait affecter les performances.

Phil Haselden
la source
84
create unique index UIX on MyTable (Column1) where Column1 is not null
Jørn Schou-Rode
1
Remarque: actuellement, SQL Server Management Studio ne semble pas savoir comment créer de tels index, donc si vous modifiez plus tard la table, elle deviendra confuse et essaiera de la supprimer, alors n'oubliez pas de la recréer
Simon_Weaver
3
Il semble que Microsoft a mis à jour SSMS pour prendre en charge cela. J'ai SSMS 10.50.1617 et dans la boîte de dialogue Propriétés de l'index, vous pouvez sélectionner la page Filtre pour modifier le filtre. par exemple "([Column1] IS NOT NULL)"
Phil Haselden
5
Autoriser plusieurs valeurs nulles dans un index et filtrer les valeurs nulles à partir d'un index sont des choses distinctes. Le filtrage d'un index exclut en fait les enregistrements de l'index, tandis que les autres solutions transforment la valeur null en une valeur unique utile. Soyez conscient de la différence.
Suncat2000
Si vous utilisez des procédures stockées sur une table avec un index filtré comme celui-ci, assurez-vous que ANSI_NULLSc'est le ONcas, sinon vous obtiendrez une erreur en essayant d'insérer des données.
Arne
71

L'astuce de colonne calculée est largement connue sous le nom de "nullbuster"; mes notes créditent Steve Kass:

CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X  int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)
un jour quand
la source
Cela ressemble à un truc sympa. Curieusement, rechercher nullbuster n'apporte pas trop de choses. Je me demande si cela sera également utile pour accélérer les recherches - plutôt qu'une colonne calculée de seulement 1 et 0 pour null ou non, si l'utilisation du PK donne à l'index quelque chose de plus avec lequel travailler? Aller tester ce week-end sur une grande table et voir.
David Storfer
@DavidStorfer, vous ne pouvez pas faire cela car vous pourriez avoir une collision entre les ID des deux tables différentes.
user393274
Amélioration: ISNULL (X, CONVERT (VARCHAR (10), pk))
Faiz
5
@Faiz: L'amélioration est dans l'œil du spectateur. Je préfère le look de l'original.
jour du
@NunoG, cela devrait être la réponse acceptée car elle fournit une bonne solution conforme à vos exigences, au lieu de simplement lier un site externe qui peut disparaître.
Frédéric
-3

À proprement parler, une colonne NULL unique (ou un ensemble de colonnes) ne peut être NULL (ou un enregistrement de NULL) qu'une seule fois, car avoir la même valeur (et cela inclut NULL) plus d'une fois enfreint évidemment la contrainte d'unicité.

Cependant, cela ne signifie pas que le concept de "colonnes Nullables uniques" est valide; pour l'implémenter dans n'importe quelle base de données relationnelle, nous devons juste garder à l'esprit que ce type de bases de données est censé être normalisé pour fonctionner correctement, et la normalisation implique généralement l'ajout de plusieurs tables supplémentaires (non-entité) pour établir des relations entre les entités .

Prenons un exemple de base en considérant une seule "colonne Nullable unique", il est facile de l'étendre à plusieurs colonnes de ce type.

Supposons que nous les informations représentées par un tableau comme celui-ci:

create table the_entity_incorrect
(
  id integer,
  uniqnull integer null, /* we want this to be "unique and nullable" */
  primary key (id)
);

Nous pouvons le faire en mettant uniqnull à part et en ajoutant une deuxième table pour établir une relation entre les valeurs uniqnull et the_entity (plutôt que d'avoir uniqnull "inside" the_entity):

create table the_entity
(
  id integer,
  primary key(id)
);

create table the_relation
(
  the_entity_id integer not null,
  uniqnull integer not null,

  unique(the_entity_id),
  unique(uniqnull),
  /* primary key can be both or either of the_entity_id or uniqnull */
  primary key (the_entity_id, uniqnull), 
  foreign key (the_entity_id) references the_entity(id)
);

Pour associer une valeur de uniqnull à une ligne dans the_entity, nous devons également ajouter une ligne dans the_relation.

Pour les lignes de the_entity où aucune valeur uniqnull n'est associée (c'est-à-dire pour celles que nous placerions NULL dans the_entity_incorrect), nous n'ajoutons simplement pas de ligne dans the_relation.

Notez que les valeurs pour uniqnull seront uniques pour toute la_relation, et notez également que pour chaque valeur de l'entité, il peut y avoir au plus une valeur dans la_relation, puisque les clés primaires et étrangères sur celle-ci imposent cela.

Ensuite, si une valeur de 5 pour uniqnull doit être associée à un id the_entity de 3, nous devons:

start transaction;
insert into the_entity (id) values (3); 
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;

Et, si une valeur id de 10 pour the_entity n'a pas d'équivalent unique, nous faisons seulement:

start transaction;
insert into the_entity (id) values (10); 
commit;

Pour dénormaliser ces informations et obtenir les données qu'une table comme the_entity_incorrect contiendrait, nous devons:

select
  id, uniqnull
from
  the_entity left outer join the_relation
on
  the_entity.id = the_relation.the_entity_id
;

L'opérateur "jointure externe gauche" garantit que toutes les lignes de l'entité apparaîtront dans le résultat, en plaçant NULL dans la colonne uniqnull lorsqu'aucune colonne correspondante n'est présente dans the_relation.

N'oubliez pas que tout effort passé pendant quelques jours (ou semaines ou mois) à concevoir une base de données bien normalisée (et les vues et procédures de dénormalisation correspondantes) vous fera économiser des années (ou des décennies) de douleur et de gaspillage de ressources.

roy
la source
6
Comme déjà indiqué sur le commentaire de la réponse acceptée avec cinquante votes positifs, il devrait être pris en charge par MS Sql Server pour avoir plusieurs null dans une colonne indexée comme unique. C'est un échec de mettre en œuvre les normes SQL de ne pas le permettre. Null n'est pas une valeur, null n'est pas égal à null, c'est une règle SQL de base depuis des années. Donc, votre première phrase est fausse et la plupart des lecteurs ne prendront pas la peine de lire.
Frédéric