Adicione restrição exclusiva à combinação de duas colunas

149

Eu tenho uma mesa e, de alguma forma, a mesma pessoa entrou Personduas vezes na minha mesa. No momento, a chave primária é apenas uma numeração automática, mas existem outros dois campos que quero forçar a serem únicos.

Por exemplo, os campos são:

ID  
Name  
Active  
PersonNumber  

Quero apenas 1 registro com um PersonNumber exclusivo e Active = 1.
(Portanto, a combinação dos dois campos precisa ser única)

Qual é a melhor maneira em uma tabela existente no SQL Server? Posso fazê-lo, se alguém fizer uma inserção com o mesmo valor que um valor existente, ele falhará, para que não precise me preocupar com isso no código do meu aplicativo.

leora
fonte
3
Você ainda precisa se preocupar com isso no código do aplicativo.
Dan Bracuk
2
Certo, "não precisa se preocupar com isso" significa o que? Se um usuário tentar inserir uma duplicata e o SQL Server não fizer isso, você não quer dizer a eles? Parece que o aplicativo precisa se preocupar com isso.
Aaron Bertrand

Respostas:

219

Depois de remover suas duplicatas:

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

ou

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

Obviamente, pode ser melhor verificar essa violação primeiro, antes de deixar o SQL Server tentar inserir a linha e retornar uma exceção (as exceções são caras).

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/

Se você deseja impedir que as exceções sejam transmitidas para o aplicativo, sem fazer alterações no aplicativo, você pode usar um INSTEAD OFgatilho:

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

Mas se você não informar ao usuário que ele não executou a inserção, eles se perguntarão por que os dados não estão lá e nenhuma exceção foi relatada.


EDITAR aqui é um exemplo que faz exatamente o que você está pedindo, mesmo usando os mesmos nomes da sua pergunta, e a prova. Você deve experimentá-lo antes de assumir que as idéias acima tratam apenas uma coluna ou outra em oposição à combinação ...

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

Dados na tabela depois de tudo isso:

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

Mensagem de erro na última inserção:

Msg 2627, Nível 14, Estado 1, Linha 3 Violação da restrição UNIQUE KEY 'uq_Person'. Não é possível inserir chave duplicada no objeto 'dbo.Person'. A instrução foi encerrada.

Aaron Bertrand
fonte
3
@leora sim, tenho certeza de que minha resposta lida com a exclusividade em duas colunas .
Aaron Bertrand #
2
não é que cada coluna precise ser única, é a combinação (concatenação) de colunas que deve ser única. Isso faz sentido . .
Leora
14

Isso também pode ser feito na GUI:

  1. Sob a tabela "Pessoa", clique com o botão direito do mouse em Índices
  2. Clique / passe o mouse em Novo Índice
  3. Clique em Índice não clusterizado ...

insira a descrição da imagem aqui

  1. Um nome de índice padrão será fornecido, mas você poderá alterá-lo.
  2. Marque a caixa de seleção Exclusivo
  3. Clique no botão Adicionar ...

insira a descrição da imagem aqui

  1. Marque as colunas que você deseja incluir

insira a descrição da imagem aqui

  1. Clique em OK em cada janela.
Tony L.
fonte
1
qual é a diferença entre restrição exclusiva e índice exclusivo? Porque quando você define Restrição Única, ele tem uma limitação de 900 bytes, mas parece que o Índice Único não possui.
batmaci
2
Nada Consulte este artigo para referência: blog.sqlauthority.com/2007/04/26/…
Eli
O meu new Indexnão é clicável, está desabilitado :(
Faisal
4
@Faisal feche as janelas de resultados / design que você abriu para essa tabela e tente novamente.
KalaNag
@Faisal verifique isso: stackoverflow.com/a/60014466/4654957
Diego Venâncio
3

No meu caso, eu precisava permitir muitos inativos e apenas uma combinação de duas chaves ativas, como esta:

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

Isso parece funcionar:

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

Aqui estão meus casos de teste:

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
fonte
Agradeço por incluir seu caso de teste aqui. Essa é uma prática recomendada que gostaria de ver mais respostas do Stack Overflow.
Jeremy Caney
0

E se você tiver muitas consultas de inserção, mas não desejar gerar uma mensagem de ERRO sempre, poderá fazê-lo:

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

insira a descrição da imagem aqui

Diego Venâncio
fonte