Dodaj unikalne ograniczenie do kombinacji dwóch kolumn

149

Mam stół i jakoś ta sama osoba Persondwukrotnie weszła do mojego stołu. W tej chwili klucz podstawowy to tylko autonumerowanie, ale istnieją dwa inne pola, które chcę wymusić, aby były unikalne.

Na przykład pola to:

ID  
Name  
Active  
PersonNumber  

Chcę tylko 1 rekord z unikatowym PersonNumber i Active = 1
(więc połączenie tych dwóch pól musi być unikalne)

Jaki jest najlepszy sposób na istniejącą tabelę na serwerze SQL, mogę to zrobić, więc jeśli ktoś inny wykona wstawkę o tej samej wartości co istniejąca wartość, nie powiedzie się, więc nie muszę się tym martwić w kodzie mojej aplikacji.

leora
źródło
3
Nadal musisz się tym martwić w kodzie aplikacji.
Dan Bracuk
2
Co oznacza „nie musisz się tym martwić”? Jeśli użytkownik próbuje wstawić duplikat, a SQL Server tego nie robi, nie chcesz mu o tym powiedzieć? Wygląda na to, że aplikacja musi się tym martwić.
Aaron Bertrand

Odpowiedzi:

219

Po usunięciu swoich duplikatów:

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

lub

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

Oczywiście często lepiej jest najpierw sprawdzić to naruszenie, a dopiero potem pozwolić SQL Server na wstawienie wiersza i zwrócenie wyjątku (wyjątki są kosztowne).

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/

Jeśli chcesz zapobiec propagowaniu wyjątków do aplikacji bez wprowadzania zmian w aplikacji, możesz użyć INSTEAD OFwyzwalacza:

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

Ale jeśli nie powiesz użytkownikowi, że nie wykonał wstawiania, będą się zastanawiać, dlaczego nie ma danych i nie zgłoszono żadnego wyjątku.


EDYTUJ to przykład, który robi dokładnie to, o co prosisz, nawet używając tych samych nazw, co twoje pytanie, i udowadnia to. Powinieneś to wypróbować przed założeniem, że powyższe pomysły traktują tylko jedną lub drugą kolumnę, w przeciwieństwie do kombinacji ...

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

Dane w tabeli po tym wszystkim:

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

Komunikat o błędzie na ostatniej wkładce:

Msg 2627, poziom 14, stan 1, wiersz 3 Naruszenie ograniczenia UNIQUE KEY „uq_Person”. Nie można wstawić zduplikowanego klucza do obiektu „dbo.Person”. Oświadczenie zostało zakończone.

Aaron Bertrand
źródło
3
@leora tak, jestem prawie pewien, że moja odpowiedź dotyczy wyjątkowości w dwóch kolumnach .
Aaron Bertrand
2
nie chodzi o to, że każda kolumna musi być unikalna, jej kombinacja (konkatenacja) kolumn musi być niepowtarzalna. Czy to ma sens . .
leora
14

Można to również zrobić w GUI:

  1. Pod tabelą „Osoba” kliknij prawym przyciskiem myszy Indeksy
  2. Kliknij / najedź na Nowy indeks
  3. Kliknij Indeks nieklastrowany ...

wprowadź opis obrazu tutaj

  1. Zostanie nadana domyślna nazwa indeksu, ale możesz ją zmienić.
  2. Zaznacz pole wyboru Unikalne
  3. Kliknij przycisk Dodaj ...

wprowadź opis obrazu tutaj

  1. Sprawdź kolumny, które chcesz uwzględnić

wprowadź opis obrazu tutaj

  1. Kliknij OK w każdym oknie.
Tony L.
źródło
1
Jaka jest różnica między Unique Constraint a Unique Index? Ponieważ kiedy ustawisz Unique Constraint, ma on ograniczenie do 900 bajtów, ale wygląda na to, że Unique Index go nie ma.
batmaci
2
Nic Zobacz ten artykuł w celach informacyjnych: blog.sqlauthority.com/2007/04/26/…
Eli
Mój new Indexnie jest klikalny, jest wyłączony :(
Faisal
4
@Faisal zamknij otwarte okna wyników / projektu dla tej tabeli i spróbuj ponownie.
KalaNag
@Faisal sprawdź to: stackoverflow.com/a/60014466/4654957
Diego Venâncio
3

W moim przypadku musiałem zezwolić na wiele nieaktywnych i tylko jedną kombinację dwóch kluczy aktywnych, na przykład:

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

To wydaje się działać:

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

Oto moje przypadki testowe:

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
user3816689
źródło
Doceniam, że dołączasz tutaj swój przypadek testowy. To najlepsza praktyka. Chciałbym zobaczyć więcej odpowiedzi na temat przepełnienia stosu.
Jeremy Caney
0

A jeśli masz dużo zapytań, ale nie chcesz za każdym razem otrzymywać komunikatu ERROR, możesz to zrobić:

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

wprowadź opis obrazu tutaj

Diego Venâncio
źródło