SQL Server 2008 et versions ultérieures
Il suffit de filtrer un index unique:
CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;
Dans les versions inférieures, une vue matérialisée n'est toujours pas requise
Pour SQL Server 2005 et versions antérieures, vous pouvez le faire sans vue. Je viens d'ajouter une contrainte unique comme celle que vous demandez à l'une de mes tables. Étant donné que je souhaite l'unicité de la colonne SamAccountName
, mais que je souhaite autoriser plusieurs valeurs NULL, j'ai utilisé une colonne matérialisée plutôt qu'une vue matérialisée:
ALTER TABLE dbo.Party ADD SamAccountNameUnique
AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
UNIQUE (SamAccountNameUnique)
Vous devez simplement mettre quelque chose dans la colonne calculée qui sera garanti unique sur toute la table lorsque la colonne unique souhaitée réelle est NULL. Dans ce cas, PartyID
est une colonne d'identité et être numérique ne correspondra à aucun SamAccountName
, donc cela a fonctionné pour moi. Vous pouvez essayer votre propre méthode - assurez-vous de bien comprendre le domaine de vos données afin qu'il n'y ait aucune possibilité d'intersection avec des données réelles. Cela pourrait être aussi simple que d'ajouter un caractère différenciateur comme celui-ci:
Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))
Même si un jour, il PartyID
devenait non numérique et pouvait coïncider avec un SamAccountName
, maintenant cela n'aurait plus d'importance.
Notez que la présence d'un index incluant la colonne calculée entraîne implicitement l'enregistrement de chaque résultat d'expression sur le disque avec les autres données de la table, ce qui prend de l'espace disque supplémentaire.
Notez que si vous ne voulez pas d'index, vous pouvez toujours économiser de l'UC en faisant précalculer l'expression sur le disque en ajoutant le mot-clé PERSISTED
à la fin de la définition de l'expression de colonne.
Dans SQL Server 2008 et versions ultérieures, utilisez plutôt la solution filtrée si vous le pouvez!
Controverse
Veuillez noter que certains professionnels de la base de données verront cela comme un cas de «NULL de substitution», qui ont certainement des problèmes (principalement en raison de problèmes pour essayer de déterminer quand quelque chose est une valeur réelle ou une valeur de substitution pour les données manquantes ; il peut également y avoir des problèmes avec le nombre de valeurs de substitution non NULL se multipliant comme un fou).
Cependant, je crois que ce cas est différent. La colonne calculée que j'ajoute ne sera jamais utilisée pour déterminer quoi que ce soit. Il n'a aucune signification en soi et n'encode aucune information qui n'est pas déjà trouvée séparément dans d'autres colonnes correctement définies. Il ne doit jamais être sélectionné ou utilisé.
Donc, mon histoire est que ce n'est pas un NULL de substitution, et je m'y tiens! Puisque nous ne voulons pas réellement la valeur non NULL à d'autres fins que de tromper l' UNIQUE
index pour ignorer les valeurs NULL, notre cas d'utilisation n'a aucun des problèmes qui surviennent avec la création NULL de substitution normale.
Cela dit, je n'ai pas de problème à utiliser une vue indexée à la place, mais cela pose certains problèmes, comme l'exigence d'utilisation SCHEMABINDING
. Amusez-vous à ajouter une nouvelle colonne à votre table de base (vous devrez au minimum supprimer l'index, puis supprimer la vue ou modifier la vue pour ne pas être liée au schéma). Consultez la liste complète (longue) des exigences pour la création d'une vue indexée dans SQL Server (2005) (également les versions ultérieures), (2000) .
Mise à jour
Si votre colonne est numérique, il peut être difficile de vous assurer que la contrainte unique utilisant Coalesce
n'entraîne pas de collisions. Dans ce cas, il existe quelques options. L'une pourrait consister à utiliser un nombre négatif, à ne mettre les «NULL de substitution» que dans la plage négative et les «valeurs réelles» uniquement dans la plage positive. Alternativement, le modèle suivant pourrait être utilisé. Dans le tableau Issue
(où IssueID
est le PRIMARY KEY
), il peut y avoir ou non un TicketID
, mais s'il y en a un, il doit être unique.
ALTER TABLE dbo.Issue ADD TicketUnique
AS (CASE WHEN TicketID IS NULL THEN IssueID END);
ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull
UNIQUE (TicketID, TicketUnique);
Si IssueID 1 a le ticket 123, la UNIQUE
contrainte sera sur les valeurs (123, NULL). Si IssueID 2 n'a pas de ticket, il sera activé (NULL, 2). Certaines réflexions montreront que cette contrainte ne peut être dupliquée pour aucune ligne du tableau et autorise toujours plusieurs NULL.