J'ai une table qui a actuellement des valeurs en double dans une colonne.
Je ne peux pas supprimer ces doublons erronés, mais j'aimerais empêcher l'ajout de valeurs non uniques supplémentaires.
Puis-je créer un UNIQUE
qui ne vérifie pas la conformité existante?
J'ai essayé d'utiliser NOCHECK
mais j'ai échoué.
Dans ce cas, j'ai un tableau qui lie les informations de licence à "CompanyName"
EDIT: Avoir plusieurs lignes avec le même "CompanyName" est une mauvaise donnée, mais nous ne pouvons pas supprimer ou mettre à jour ces doublons pour le moment. Une approche consiste à faire en sorte que les INSERT
utilisent une procédure stockée qui échouera pour les doublons ... S'il était possible que SQL vérifie seul l'unicité, ce serait préférable.
Ces données sont interrogées par nom d'entreprise. Pour les quelques doublons existants, cela signifie que plusieurs lignes sont renvoyées et affichées ... Bien que ce soit faux, c'est acceptable dans notre cas d'utilisation. Le but est de l'empêcher à l'avenir. Il me semble d'après les commentaires que je dois faire cette logique dans les procédures stockées.
La réponse est oui". Vous pouvez le faire avec un index filtré (voir ici pour la documentation).
Par exemple, vous pouvez faire:
create unique index t_col on t(col) where id > 1000;
Cela crée un index unique, uniquement sur les nouvelles lignes, plutôt que sur les anciennes lignes. Cette formulation particulière permettrait des doublons avec les valeurs existantes.
Si vous n'avez qu'une poignée de doublons, vous pouvez faire quelque chose comme:
create unique index t_col on t(col) where id not in (<list of ids for duplicate values here>);
Oui, tu peux faire ça.
Voici un tableau avec des doublons:
CREATE TABLE dbo.Party
(
ID INT NOT NULL
IDENTITY ,
CONSTRAINT PK_Party PRIMARY KEY ( ID ) ,
Name VARCHAR(30) NOT NULL
) ;
GO
INSERT INTO dbo.Party
( Name )
VALUES ( 'Frodo Baggins' ),
( 'Luke Skywalker' ),
( 'Luke Skywalker' ),
( 'Harry Potter' ) ;
GO
Ignorons les existants et veillons à ce qu'aucun nouveau doublon ne puisse être ajouté:
-- Add a new column to mark grandfathered duplicates.
ALTER TABLE dbo.Party ADD IgnoreThisDuplicate INT NULL ;
GO
-- The *first* instance will be left NULL.
-- *Secondary* instances will be set to their ID (a unique value).
UPDATE dbo.Party
SET IgnoreThisDuplicate = ID
FROM dbo.Party AS my
WHERE EXISTS ( SELECT *
FROM dbo.Party AS other
WHERE other.Name = my.Name
AND other.ID < my.ID ) ;
GO
-- This constraint is not strictly necessary.
-- It prevents granting further exemptions beyond the ones we made above.
ALTER TABLE dbo.Party WITH NOCHECK
ADD CONSTRAINT CHK_Party_NoNewExemptions
CHECK(IgnoreThisDuplicate IS NULL);
GO
SELECT * FROM dbo.Party;
GO
-- **THIS** is our pseudo-unique constraint.
-- It works because the grandfathered duplicates have a unique value (== their ID).
-- Non-grandfathered records just have NULL, which is not unique.
CREATE UNIQUE INDEX UNQ_Party_UniqueNewNames ON dbo.Party(Name, IgnoreThisDuplicate);
GO
Essayons cette solution:
-- cannot add a name that exists
INSERT INTO dbo.Party
( Name )
VALUES ( 'Frodo Baggins' );
Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.
-- cannot add a name that exists and has an ignored duplicate
INSERT INTO dbo.Party
( Name )
VALUES ( 'Luke Skywalker' );
Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.
-- can add a new name
INSERT INTO dbo.Party
( Name )
VALUES ( 'Hamlet' );
-- but only once
INSERT INTO dbo.Party
( Name )
VALUES ( 'Hamlet' );
Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.
L'index unique filtré est une excellente idée, mais il présente un inconvénient mineur - peu importe si vous utilisez la condition WHERE identity_column > <current value>
Ou la WHERE identity_column NOT IN (<list of ids for duplicate values here>)
.
Avec la première approche, vous pourrez toujours insérer des données en double à l'avenir, des doublons de données existantes (maintenant). Par exemple, si vous avez (même une seule) ligne maintenant avec CompanyName = 'Software Inc.'
, L'index n'interdira pas l'insertion d'une ligne de plus avec le même nom de société. Il ne l'interdira que si vous essayez deux fois.
Avec la deuxième approche, il y a une amélioration, ce qui précède ne fonctionnera pas (ce qui est bien.) Cependant, vous pourrez toujours insérer plus de doublons ou de doublons existants. Par exemple, si vous avez maintenant (deux ou plusieurs) lignes avec CompanyName = 'DoubleData Co.'
, L'index n'interdira pas l'insertion d'une ligne de plus avec le même nom de société. Il ne l'interdira que si vous essayez deux fois.
(Mise à jour) Cela peut être corrigé si pour chaque nom en double, vous gardez hors de la liste d'exclusion un identifiant. Si, comme dans l'exemple ci-dessus, il y a 4 lignes avec CompanyName = DoubleData Co.
Et ID 4,6,8,9
En double, la liste d'exclusion ne doit contenir que 3 de ces ID.
Avec la deuxième approche, un autre inconvénient est la condition encombrante (la quantité d'encombrement dépend du nombre de doublons en premier lieu), car SQL-Server semble ne pas prendre en charge l'opérateur NOT IN
Dans le WHERE
partie d'index filtrés. Voir SQL-Fiddle . Au lieu de WHERE (CompanyID NOT IN (3,7,4,6,8,9))
, vous devrez avoir quelque chose comme WHERE (CompanyID <> 3 AND CompanyID <> 7 AND CompanyID <> 4 AND CompanyID <> 6 AND CompanyID <> 8 AND CompanyID <> 9)
Je ne sais pas s'il y a des implications d'efficacité avec une telle condition, si vous avez des centaines de noms en double.
Une autre solution (similaire à celle de @Alex Kuznetsov) consiste à ajouter une autre colonne, à la remplir avec des numéros de classement et à ajouter un index unique comprenant cette colonne:
ALTER TABLE Company
ADD Rn TINYINT DEFAULT 1;
UPDATE x
SET Rn = Rnk
FROM
( SELECT
CompanyID,
Rn,
Rnk = ROW_NUMBER() OVER (PARTITION BY CompanyName
ORDER BY CompanyID)
FROM Company
) x ;
CREATE UNIQUE INDEX CompanyName_UQ
ON Company (CompanyName, Rn) ;
Ensuite, l'insertion d'une ligne avec un nom en double échouera en raison de la propriété DEFAULT 1
Et de l'index unique. Ce n'est toujours pas infaillible à 100% (contrairement à celui d'Alex). Les doublons se glisseront toujours si Rn
est explicitement défini dans l'instruction INSERT
ou si les valeurs Rn
sont mises à jour de manière malveillante.