9

Je l'ai eu à quelques reprises dans ma carrière, et aucun de mes pairs locaux ne semble être en mesure d'y répondre. Dites que j'ai une table qui a un champ "Description" qui est une clé candidate, sauf que parfois un utilisateur s'arrêtera à mi-chemin du processus. Donc, pour peut-être 25% des enregistrements, cette valeur est nulle, mais pour tout ce qui n'est pas NULL, elle doit être unique.SQL puis-je avoir une contrainte "conditionnellement unique" sur une table?

Un autre exemple pourrait être une table qui doit conserver plusieurs "versions" d'un enregistrement, et une valeur de bit indique laquelle est la "active". Ainsi, la "clé candidate" est toujours remplie, mais il peut y avoir trois versions identiques (avec 0 dans le bit actif) et une seule active (1 dans le bit actif). J'ai d'autres méthodes pour résoudre ces problèmes (dans le premier cas, appliquer le code de la règle, soit dans la procédure stockée ou la couche de gestion, et dans la seconde, remplir une table d'archive avec un déclencheur et UNION les tables lorsque je besoin d'une histoire). Je ne veux pas d'alternatives (à moins qu'il n'y ait des solutions manifestement meilleures), je me demande simplement si toute saveur de SQL peut exprimer de la sorte un «unicité conditionnel». J'utilise MS SQL, donc s'il y a un moyen de le faire, super. Je suis surtout intéressé par le problème.

+2

double possible: http://stackoverflow.com/questions/866061/conditional-unique-constraint –

+1

Quelle version de SQL Server? – gbn

+0

Merci pour le pointeur In silico. Cela s'applique certainement à la situation 2. J'ai fait une recherche avant de poster, mais je n'ai pas fait de recherche sur les mots-clés que j'ai éventuellement utilisés dans le titre ou je l'aurais trouvé! Le lien que vous postez n'aborde pas tout à fait la situation # 1, qui est résolue avec la suggestion de Tom H. en 2005 et Arthur en 2008. gbn: J'utilise 2005, c'est pourquoi je ne connaissais pas l'index filtres en 2008. –

Répondre

28

Si vous utilisez SQL Server 2008, un filtre d'index serait peut-être votre solution:

http://msdn.microsoft.com/en-us/library/ms188783.aspx

Voici comment j'appliquer un index unique avec plusieurs valeurs NULL

CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL 
+1

Ah - index basé sur la fonction ... DBA déteste ceux –

+0

@OMG Poneys pour les DBA c'est presque la seule solution viable – msarchet

+0

@msarchet: Le travail fait obstacle à l'ATM, mais la configuration de table décrite dans le PO est pas idéal pour moi. Mais aimez vous battre avec les DBA =) –

0

Merci pour les commentaires, la version initiale de cette réponse était erronée.

Voici une astuce en utilisant une colonne calculée qui permet effectivement une contrainte unique dans annulable SQL Server:

create table NullAndUnique 
    (
    id int identity, 
    name varchar(50), 
    uniqueName as case 
     when name is null then cast(id as varchar(51)) 
     else name + '_' end, 
    unique(uniqueName) 
    ) 

insert into NullAndUnique default values 
insert into NullAndUnique default values -- Works 
insert into NullAndUnique default values -- not accidentally :) 
insert into NullAndUnique (name) values ('Joel') 
insert into NullAndUnique (name) values ('Joel') -- Boom! 

Ils utilisent les id lorsque la name est nulle. Le + '_' est d'éviter les cas où le nom pourrait être numérique, comme 1, qui pourrait entrer en collision avec le id.

+1

Vraiment? De MSDN: "Les colonnes utilisées dans un index unique doivent être définies sur NOT NULL, car plusieurs valeurs NULL sont considérées comme des doublons lorsqu'un index unique est créé." – Arthur

+6

Non, SQL Server n'autorise * PAS * plusieurs valeurs NULL dans une colonne couverte par une contrainte unique. La norme SQL spécifie que cela doit être autorisé (car NULL n'est pas égal à lui-même), mais Microsoft SQL Server n'a pas suivi la norme sur ce point. –

0

L'autre méthode Pour appliquer des règles conditionnelles qu'un index ou une contrainte de vérification unique ne peut pas gérer, utilisez un déclencheur.

1

Oracle le fait. Une clé entièrement nulle n'est pas indexée par un arbre B dans Oracle dans Oracle, et Oracle utilise B des index d'arborescence pour appliquer des contraintes uniques.

supposant que l'on voulait à la version ID_COLUMN sur la base du ACTIVE_FLAG étant fixé à 1:

CREATE UNIQUE INDEX idx_versioning_id ON mytable 
    (CASE active_flag WHEN 0 THEN NULL ELSE active_flag END, 
    CASE active_flag WHEN 0 THEN NULL ELSE id_column END); 
2

Dans le cas des descriptions qui ne sont pas encore terminés, je ne voudrais pas avoir ceux de la même table que la finalisation descriptions. La table finale aurait alors un index unique ou une clé primaire sur la description. Dans le cas de l'actif/inactif, encore une fois je pourrais avoir des tables séparées comme vous l'avez fait avec une table "archive" ou "history", mais une autre façon possible de le faire dans MS SQL Server est au moins par l'utilisation d'une vue indexée:

CREATE TABLE Test_Conditionally_Unique 
(
    my_id INT NOT NULL, 
    active BIT NOT NULL DEFAULT 0 
) 
GO 
CREATE VIEW dbo.Test_Conditionally_Unique_View 
WITH SCHEMABINDING 
AS 
    SELECT 
     my_id 
    FROM 
     dbo.Test_Conditionally_Unique 
    WHERE 
     active = 1 
GO 
CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id) 
GO 

INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 1) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 1) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 1) -- This insert will fail 

Vous pouvez également utiliser cette même méthode pour les descriptions NULL/Valued.

+0

Merci, cela fonctionne sur 2005, et j'étais en train de jouer avec des vues indexées aujourd'hui sur un problème différent. C'est une très bonne application de ça! –

0

Je ne suis pas entièrement au courant de votre utilisation prévue ou de vos tables, mais vous pourriez essayer d'utiliser une relation un à un. Diviser cette colonne "parfois" unique en une nouvelle table, créer l'index UNIQUE sur cette colonne dans la nouvelle table et FK revenir à la table d'origine en utilisant les tables d'origine PK. N'ayez qu'une ligne dans cette nouvelle table lorsque les données "uniques" sont supposées exister.

tables VIEUX:

TableA 
ID pk 
Col1 sometimes unique 
Col... 

tables NOUVEAU:

TableA 
ID 
Col... 

TableB 
ID PK, FK to TableA.ID 
Col1 unique index