219

I have a table and, somehow, the same person got into my Person table twice. Right now, the primary key is just an autonumber but there are two other fields that exist that I want to force to be unique.

For example, the fields are:

ID  
Name  
Active  
PersonNumber  

I only want 1 record with a unique PersonNumber and Active = 1.
(So the combination of the two fields needs to be unique)

What is the best way on an existing table in SQL server I can make it so if anyone else does an insert with the same value as an existing value, it fails so I don't have to worry about this in my application code.

Tony L.
  • 17,638
  • 8
  • 69
  • 66
leora
  • 188,729
  • 360
  • 878
  • 1,366
  • 5
    You still have to worry about it in your application code. – Dan Bracuk Apr 03 '13 at 23:59
  • 3
    Right, "don't have to worry about it" means what? If a user *tries* to insert a duplicate, and SQL Server doesn't do it, don't you want to tell them? Sounds like the application needs to worry about it. – Aaron Bertrand Apr 04 '13 at 00:01

4 Answers4

326

Once you have removed your duplicate(s):

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

or

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

Of course, it can often be better to check for this violation first, before just letting SQL Server try to insert the row and returning an exception (exceptions are expensive).

If you want to prevent exceptions from bubbling up to the application, without making changes to the application, you can use an INSTEAD OF trigger:

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

But if you don't tell the user they didn't perform the insert, they're going to wonder why the data isn't there and no exception was reported.


EDIT here is an example that does exactly what you're asking for, even using the same names as your question, and proves it. You should try it out before assuming the above ideas only treat one column or the other as opposed to the combination...

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

Data in the table after all of this:

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

Error message on the last insert:

Msg 2627, Level 14, State 1, Line 3 Violation of UNIQUE KEY constraint 'uq_Person'. Cannot insert duplicate key in object 'dbo.Person'. The statement has been terminated.

Also I blogged more recently about a solution to applying a unique constraint to two columns in either order:

Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
  • 3
    @leora yes, I'm pretty sure my answer deals with uniqueness on two *columns*. – Aaron Bertrand Apr 04 '13 at 00:06
  • 3
    its not that each column has to be unique, its the combination (concatenation) of columns has to be unique. does that make sense . . – leora Apr 04 '13 at 00:07
  • @AaronBertrand Thanks for reply. Imagine my 2 fields are 2 user ids (I am creating a Chat and a chat has 2 users). How can I use UNIQUE(column1, column2); but working also the other way? Something like UNIQUE(column1, column2) AND UNIQUE(column2, column1). Any suggestion? Thanks a lot. – Ricardo Jan 05 '21 at 10:06
  • 1
    @Ricardo Are they just two columns that represent any two users in a chat? Or does one column have relevance (like the chat initiator)? If order doesn't matter then you could also use logic to make sure user1 < user2, then you would only ever need one of the constraints. If you need both directions for some reason, then you could add computed (non-persisted) columns that are forced to be in a specific order, then put the unique constraint there. e.g. `u1c AS CASE WHEN user1 < user2 THEN user1 ELSE user2 END` and `u2c AS CASE WHEN user1 < user2 THEN user2 ELSE user1 END` – Aaron Bertrand Jan 05 '21 at 13:38
  • 1
    @Ricardo I wrote up an article about this in February, sorry I forgot to come back and ping you once it published: [Enforce a Unique Constraint Where Order Does Not Matter](https://www.mssqltips.com/sqlservertip/6737/sql-unique-constraint-where-order-does-not-matter/). – Aaron Bertrand Sep 13 '21 at 16:16
  • This doesn't work for an Oracle DB. – Collin Nov 01 '22 at 20:09
  • @CollinFox very true! But the question isn’t about Oracle. It also doesn’t work for MongoDB or FoxPro, because the question isn’t about those platforms, either. – Aaron Bertrand Nov 02 '22 at 00:25
20

This can also be done in the GUI:

  1. Under the table "Person", right click Indexes
  2. Click/hover New Index
  3. Click Non-Clustered Index...

enter image description here

  1. A default Index name will be given but you may want to change it.
  2. Check Unique checkbox
  3. Click Add... button

enter image description here

  1. Check the columns you want included

enter image description here

  1. Click OK in each window.
Tony L.
  • 17,638
  • 8
  • 69
  • 66
  • 1
    what is the difference between Unique Constraint and Unique Index? Because when you set Unique Constraint, it has 900 Bytes limitation but It looks like Unique Index doesnt have. – Emil Dec 14 '16 at 14:16
  • 3
    Nothing See this article for reference: https://blog.sqlauthority.com/2007/04/26/sql-server-difference-between-unique-index-vs-unique-constraint/ – Eli Sep 13 '17 at 14:04
  • 1
    My `new Index` is not clickable it's disabled :( – Moeez Mar 29 '19 at 07:32
  • 7
    @Faisal close the results/design windows you have open for that table and try again. – KalaNag Sep 20 '19 at 20:42
  • @Faisal check this: https://stackoverflow.com/a/60014466/4654957 – Diego Venâncio Feb 01 '20 at 06:06
5

In my case, I needed to allow many inactives and only one combination of two keys active, like this:

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

This seems to work:

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

Here are my test cases:

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
Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
user3816689
  • 61
  • 1
  • 2
  • 1
    I appreciate you including your test case here. That’s a best practice I’d like to see more Stack Overflow answers adopt. – Jeremy Caney May 14 '20 at 17:22
1

And if you have lot insert queries but not wanna ger a ERROR message everytime , you can do it:

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

enter image description here

Diego Venâncio
  • 5,698
  • 2
  • 49
  • 68