Agregue una restricción única a la combinación de dos columnas.

149

Tengo una mesa y, de alguna manera, la misma persona se metió en mi Personmesa dos veces. En este momento, la clave principal es solo un número automático, pero existen otros dos campos que quiero forzar para que sean únicos.

Por ejemplo, los campos son:

ID  
Name  
Active  
PersonNumber  

Solo quiero 1 registro con un PersonNumber único y Active = 1.
(Por lo tanto, la combinación de los dos campos debe ser única)

¿Cuál es la mejor manera en una tabla existente en el servidor SQL? Puedo hacerlo, así que si alguien más hace una inserción con el mismo valor que un valor existente, falla, así que no tengo que preocuparme por esto en mi código de aplicación.

leora
fuente
3
Todavía tiene que preocuparse por eso en su código de aplicación.
Dan Bracuk
2
Bien, "no tienes que preocuparte por eso", ¿qué significa? Si un usuario intenta insertar un duplicado y SQL Server no lo hace, ¿no quiere decirle? Parece que la aplicación necesita preocuparse por eso.
Aaron Bertrand

Respuestas:

219

Una vez que haya eliminado sus duplicados:

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

o

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

Por supuesto, a menudo puede ser mejor verificar primero esta violación, antes de dejar que SQL Server intente insertar la fila y devolver una excepción (las excepciones son costosas).

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 desea evitar que surjan excepciones en la aplicación, sin realizar cambios en la aplicación, puede usar un INSTEAD OFactivador:

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

Pero si no le dice al usuario que no realizó la inserción, se preguntará por qué los datos no están allí y no se informó ninguna excepción.


EDITAR aquí es un ejemplo que hace exactamente lo que está pidiendo, incluso usando los mismos nombres que su pregunta, y lo demuestra. Debe probarlo antes de asumir que las ideas anteriores solo tratan una columna u otra en lugar de la combinación ...

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

Datos en la tabla después de todo esto:

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

Mensaje de error en la última inserción:

Mensaje 2627, Nivel 14, Estado 1, Línea 3 Infracción de la restricción ÚNICA CLAVE 'uq_Person'. No se puede insertar una clave duplicada en el objeto 'dbo.Person'. La instrucción se ha terminado.

Aaron Bertrand
fuente
3
@leora sí, estoy bastante seguro de que mi respuesta trata sobre la unicidad en dos columnas .
Aaron Bertrand
2
no es que cada columna tenga que ser única, sino que la combinación (concatenación) de columnas debe ser única. Tiene sentido . .
leora
14

Esto también se puede hacer en la GUI:

  1. Debajo de la tabla "Persona", haga clic con el botón derecho en Índices
  2. Click / hover Nuevo índice
  3. Haga clic en Índice no agrupado ...

ingrese la descripción de la imagen aquí

  1. Se le dará un nombre de índice predeterminado , pero es posible que desee cambiarlo.
  2. Marque la casilla de verificación Único
  3. Haga clic en el botón Agregar ...

ingrese la descripción de la imagen aquí

  1. Marque las columnas que desea incluir

ingrese la descripción de la imagen aquí

  1. Haga clic en Aceptar en cada ventana.
Tony L.
fuente
1
¿Cuál es la diferencia entre la restricción única y el índice único? Porque cuando configura la restricción única, tiene una limitación de 900 bytes, pero parece que Unique Index no tiene.
batmaci
2
Nada Vea este artículo para referencia: blog.sqlauthority.com/2007/04/26/…
Eli
new IndexNo se puede hacer clic en My, está desactivado :(
Faisal
44
@Faisal cierre las ventanas de resultados / diseño que haya abierto para esa tabla e intente nuevamente.
KalaNag
@Faisal revisa esto: stackoverflow.com/a/60014466/4654957
Diego Venâncio
3

En mi caso, necesitaba permitir muchas inactivas y solo una combinación de dos teclas activas, como esta:

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

Esto 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;

Aquí están mis casos de prueba:

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
usuario3816689
fuente
Le agradezco que incluya su caso de prueba aquí. Esa es una buena práctica. Me gustaría ver que se adopten más respuestas de Stack Overflow.
Jeremy Caney
0

Y si tiene muchas consultas de inserción pero no desea enviar un mensaje de ERROR cada vez, puede hacerlo:

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

ingrese la descripción de la imagen aquí

Diego Venâncio
fuente