Aggiungi un vincolo univoco alla combinazione di due colonne


149

Ho un tavolo e, in qualche modo, la stessa persona è entrata nel mio Persontavolo due volte. In questo momento, la chiave primaria è solo un numero automatico ma esistono altri due campi che voglio forzare per essere univoci.

Ad esempio, i campi sono:

ID  
Name  
Active  
PersonNumber  

Voglio solo 1 record con un PersonNumber univoco e Active = 1.
(Quindi la combinazione dei due campi deve essere unica)

Qual è il modo migliore su una tabella esistente nel server SQL che posso fare in modo che se qualcun altro fa un inserimento con lo stesso valore di un valore esistente, fallisce, quindi non devo preoccuparmi di questo nel mio codice dell'applicazione.


3
Devi ancora preoccupartene nel codice dell'applicazione.
Dan Bracuk,

2
Giusto, "non preoccuparti" significa cosa? Se un utente tenta di inserire un duplicato e SQL Server non lo fa, non vuoi dirglielo? Sembra che l'applicazione debba preoccuparsene.
Aaron Bertrand,

Risposte:


219

Dopo aver rimosso i duplicati:

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

o

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

Naturalmente, spesso può essere meglio verificare prima questa violazione, prima di lasciare che SQL Server tenti di inserire la riga e restituisca un'eccezione (le eccezioni sono costose).

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 si desidera impedire il bubbing delle eccezioni all'applicazione, senza apportare modifiche all'applicazione, è possibile utilizzare un INSTEAD OFtrigger:

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

Ma se non dici all'utente che non ha eseguito l'inserimento, si chiederanno perché i dati non sono presenti e non è stata segnalata alcuna eccezione.


EDIT qui è un esempio che fa esattamente quello che stai chiedendo, anche usando gli stessi nomi della tua domanda, e lo dimostra. Dovresti provarlo prima di assumere che le idee di cui sopra trattino solo una colonna o l'altra rispetto alla combinazione ...

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

Dati nella tabella dopo tutto questo:

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

Messaggio di errore sull'ultimo inserimento:

Messaggio 2627, livello 14, stato 1, riga 3 violazione del vincolo UNIQUE KEY 'uq_Person'. Impossibile inserire la chiave duplicata nell'oggetto 'dbo.Person'. La dichiarazione è stata chiusa.


3
@leora sì, sono abbastanza sicuro che la mia risposta riguardi l'unicità su due colonne .
Aaron Bertrand,

2
non è che ogni colonna debba essere unica, è la combinazione (concatenazione) delle colonne deve essere unica. Ha senso . .
leora,

14

Questo può essere fatto anche nella GUI:

  1. Nella tabella "Persona", fai clic con il pulsante destro del mouse su Indici
  2. Fai clic / passa con il mouse su Nuovo indice
  3. Fai clic su Indice non cluster ...

inserisci qui la descrizione dell'immagine

  1. Verrà assegnato un nome indice predefinito ma potresti volerlo cambiare.
  2. Seleziona la casella Unica
  3. Fai clic sul pulsante Aggiungi ...

inserisci qui la descrizione dell'immagine

  1. Controlla le colonne che desideri includere

inserisci qui la descrizione dell'immagine

  1. Fai clic su OK in ogni finestra.

1
qual è la differenza tra vincolo unico e indice univoco? Perché quando si imposta Vincolo univoco, ha una limitazione di 900 byte ma sembra che l'indice univoco non lo abbia.
Batmaci,

2
Niente Consulta questo articolo come riferimento: blog.sqlauthority.com/2007/04/26/…
Eli

Il mio new Indexnon è cliccabile è disabilitato :(
Faisal

4
@Faisal chiudi le finestre dei risultati / design che hai aperto per quella tabella e riprova.
KalaNag,


3

Nel mio caso, avevo bisogno di consentire molti inattivi e solo una combinazione di due chiavi attive, in questo modo:

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

Questo sembra funzionare:

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

Ecco i miei casi di 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

Ti ringrazio per aver incluso il tuo caso di test qui. Questa è una buona pratica che mi piacerebbe vedere più risposte Stack Overflow adottare.
Jeremy Caney,

0

E se hai molte domande di inserimento ma non vuoi ger ogni volta un messaggio di ERRORE, puoi farlo:

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

inserisci qui la descrizione dell'immagine

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.