Ajouter une contrainte unique à la combinaison de deux colonnes


149

J'ai une table et, d'une manière ou d'une autre, la même personne est entrée dans mon Person installée deux fois table. À l'heure actuelle, la clé primaire est simplement un numéro automatique, mais il existe deux autres champs que je veux forcer à être uniques.

Par exemple, les champs sont:

ID  
Name  
Active  
PersonNumber  

Je veux seulement 1 enregistrement avec un PersonNumber unique et Active = 1.
(La combinaison des deux champs doit donc être unique)

Quelle est la meilleure façon sur une table existante dans un serveur SQL que je puisse faire en sorte que si quelqu'un d'autre fasse une insertion avec la même valeur qu'une valeur existante, elle échoue afin que je n'ai pas à m'inquiéter à ce sujet dans le code de mon application.


3
Vous devez toujours vous en préoccuper dans le code de votre application.
Dan Bracuk

2
Bon, "ne pas avoir à vous en soucier" signifie quoi? Si un utilisateur tente d'insérer un doublon et que SQL Server ne le fait pas, ne voulez-vous pas leur dire? On dirait que l'application doit s'en soucier.
Aaron Bertrand

Réponses:


219

Une fois que vous avez supprimé votre (vos) duplicata (s):

ALTER TABLE dbo.yourtablename
  ADD CONSTRAINT uq_yourtablename UNIQUE(column1, column2);

ou

CREATE UNIQUE INDEX uq_yourtablename
  ON dbo.yourtablename(column1, column2);

Bien sûr, il peut souvent être préférable de vérifier d'abord cette violation, avant de simplement laisser SQL Server essayer d'insérer la ligne et de renvoyer une exception (les exceptions coûtent cher).

http://www.sqlperformance.com/2012/08/t-sql-queries/error-handling

http://www.mssqltips.com/sqlservertip/2632/checking-for-potential-constraint-violations-before-entering-sql-server-try-and-catch-logic/

Si vous souhaitez empêcher les exceptions de se propager dans l'application, sans apporter de modifications à l'application, vous pouvez utiliser un INSTEAD OFdéclencheur:

CREATE TRIGGER dbo.BlockDuplicatesYourTable
 ON dbo.YourTable
 INSTEAD OF INSERT
AS
BEGIN
  SET NOCOUNT ON;

  IF NOT EXISTS (SELECT 1 FROM inserted AS i 
    INNER JOIN dbo.YourTable AS t
    ON i.column1 = t.column1
    AND i.column2 = t.column2
  )
  BEGIN
    INSERT dbo.YourTable(column1, column2, ...)
      SELECT column1, column2, ... FROM inserted;
  END
  ELSE
  BEGIN
    PRINT 'Did nothing.';
  END
END
GO

Mais si vous ne dites pas à l'utilisateur qu'il n'a pas effectué l'insertion, il se demandera pourquoi les données ne sont pas là et aucune exception n'a été signalée.


EDIT voici un exemple qui fait exactement ce que vous demandez, même en utilisant les mêmes noms que votre question, et le prouve. Vous devriez l'essayer avant de supposer que les idées ci-dessus ne traitent qu'une colonne ou l'autre par opposition à la combinaison ...

USE tempdb;
GO

CREATE TABLE dbo.Person
(
  ID INT IDENTITY(1,1) PRIMARY KEY,
  Name NVARCHAR(32),
  Active BIT,
  PersonNumber INT
);
GO

ALTER TABLE dbo.Person 
  ADD CONSTRAINT uq_Person UNIQUE(PersonNumber, Active);
GO

-- succeeds:
INSERT dbo.Person(Name, Active, PersonNumber)
  VALUES(N'foo', 1, 22);
GO

-- succeeds:
INSERT dbo.Person(Name, Active, PersonNumber)
  VALUES(N'foo', 0, 22);
GO

-- fails:
INSERT dbo.Person(Name, Active, PersonNumber)
  VALUES(N'foo', 1, 22);
GO

Données dans le tableau après tout cela:

ID   Name   Active PersonNumber
---- ------ ------ ------------
1    foo    1      22
2    foo    0      22

Message d'erreur sur la dernière insertion:

Msg 2627, niveau 14, état 1, ligne 3 Violation de la contrainte UNIQUE KEY 'uq_Person'. Impossible d'insérer la clé en double dans l'objet 'dbo.Person'. La déclaration a été terminée.


3
@leora oui, je suis sûr que ma réponse traite de l'unicité sur deux colonnes .
Aaron Bertrand

2
ce n'est pas que chaque colonne doit être unique, c'est la combinaison (concaténation) des colonnes doit être unique. Cela a-t-il du sens . .
leora

14

Cela peut également être fait dans l'interface graphique:

  1. Sous le tableau "Personne", cliquez avec le bouton droit sur Index
  2. Cliquez / survolez un nouvel index
  3. Cliquez sur Index non groupé ...

entrez la description de l'image ici

  1. Un nom d'index par défaut sera donné mais vous souhaiterez peut-être le changer.
  2. Cochez la case Unique
  3. Cliquez sur le bouton Ajouter ...

entrez la description de l'image ici

  1. Vérifiez les colonnes que vous souhaitez inclure

entrez la description de l'image ici

  1. Cliquez sur OK dans chaque fenêtre.

1
Quelle est la différence entre Unique Constraint et Unique Index? Parce que lorsque vous définissez une contrainte unique, il a une limitation de 900 octets, mais il semble que l'index unique ne l'a pas.
batmaci

2
Rien Voir cet article pour référence: blog.sqlauthority.com/2007/04/26/…
Eli

Mon new Indexn'est pas cliquable, il est désactivé :(
Faisal

4
@Faisal ferme les fenêtres de résultats / conception que vous avez ouvertes pour cette table et réessayez.
KalaNag


3

Dans mon cas, je devais autoriser de nombreux inactifs et une seule combinaison de deux clés actives, comme ceci:

UUL_USR_IDF  UUL_UND_IDF    UUL_ATUAL
137          18             0
137          19             0
137          20             1
137          21             0

Cela semble fonctionner:

CREATE UNIQUE NONCLUSTERED INDEX UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL
ON USER_UND(UUL_USR_IDF, UUL_ATUAL)
WHERE UUL_ATUAL = 1;

Voici mes cas de test:

SELECT * FROM USER_UND WHERE UUL_USR_IDF = 137

insert into USER_UND values (137, 22, 1) --I CAN NOT => Cannot insert duplicate key row in object 'dbo.USER_UND' with unique index 'UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL'. The duplicate key value is (137, 1).
insert into USER_UND values (137, 23, 0) --I CAN
insert into USER_UND values (137, 24, 0) --I CAN

DELETE FROM USER_UND WHERE UUL_USR_ID = 137

insert into USER_UND values (137, 22, 1) --I CAN
insert into USER_UND values (137, 27, 1) --I CAN NOT => Cannot insert duplicate key row in object 'dbo.USER_UND' with unique index 'UQ_USR_UND_UUL_USR_IDF_UUL_ATUAL'. The duplicate key value is (137, 1).
insert into USER_UND values (137, 28, 0) --I CAN
insert into USER_UND values (137, 29, 0) --I CAN

Je vous remercie d'avoir inclus votre cas de test ici. C'est une bonne pratique que j'aimerais voir adopter davantage de réponses Stack Overflow.
Jeremy Caney

0

Et si vous avez beaucoup de requêtes d'insertion mais que vous ne voulez pas générer de message d'erreur à chaque fois, vous pouvez le faire:

CREATE UNIQUE NONCLUSTERED INDEX SK01 ON dbo.Person(ID,Name,Active,PersonNumber) 
WITH(IGNORE_DUP_KEY = ON)

entrez la description de l'image ici

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.