9

Ho avuto questo un paio di volte nella mia carriera, e nessuno dei miei colleghi locali sembra essere in grado di rispondere. Supponiamo che abbia una tabella con un campo "Descrizione" che è una chiave candidata, tranne che a volte un utente si fermerà a metà del processo. Quindi per circa il 25% dei record questo valore è nullo, ma per tutto ciò che non è NULL, deve essere univoco.SQL posso avere un vincolo "condizionalmente unico" su una tabella?

Un altro esempio potrebbe essere una tabella che deve mantenere più "versioni" di un record e un valore di bit indica quale è "attivo". Quindi la "chiave candidata" è sempre popolata, ma possono esserci tre versioni identiche (con 0 nel bit attivo) e una sola che è attiva (1 nel bit attivo).

Ho metodi alternativi per risolvere questi problemi (nel primo caso, applicare il codice della regola, sia nella stored procedure o nel livello aziendale, e nel secondo, popolare una tabella di archivio con un trigger e UNION le tabelle quando I bisogno di una storia). Non voglio alternative (a meno che non ci siano soluzioni dimostrabilmente migliori), mi chiedo solo se qualche sapore di SQL può esprimere "unicità condizionale" in questo modo. Sto usando MS SQL, quindi se c'è un modo per farlo, ottimo. Sono per lo più solo accademicamente interessato al problema.

+2

Eventuali duplicati: http://stackoverflow.com/questions/866061/conditional-unique-constraint –

+1

Quale versione di SQL Server? – gbn

+0

Grazie per il puntatore In silico. Si applica sicuramente alla situazione 2. Ho cercato prima di pubblicare, ma in realtà non ho cercato le parole chiave che ho usato nel titolo o l'avrei trovato! Il link che postate non affronta completamente la situazione n. 1, che è stata risolta con il suggerimento di Tom H. nel 2005 e Arthur nel 2008. gbn: Sto usando il 2005, motivo per cui non conoscevo l'indice filtri nel 2008. –

risposta

28

Se si utilizza SQL Server 2008 un filtro indice sarebbe forse la soluzione:

http://msdn.microsoft.com/en-us/library/ms188783.aspx

Ecco come ho rispettare un indice univoco con NULL più valori

CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL 
+1

Ah - indice basato sulla funzione ... I DBA detestano quelli –

+0

@OMG Ponies mentre succhiano per gli amministratori di database è l'unica soluzione valida – msarchet

+0

@msarchet: il lavoro sta ostacolando l'ATM, ma l'impostazione tabella descritta nell'OP è non è l'ideale per me. Ma divertiti a combattere con i DBA =) –

0

Grazie per i commenti, la versione iniziale di questa risposta era sbagliata.

Ecco un trucco usando una colonna calcolata che consente effettivamente un vincolo univoco annullabile in SQL Server:

create table NullAndUnique 
    (
    id int identity, 
    name varchar(50), 
    uniqueName as case 
     when name is null then cast(id as varchar(51)) 
     else name + '_' end, 
    unique(uniqueName) 
    ) 

insert into NullAndUnique default values 
insert into NullAndUnique default values -- Works 
insert into NullAndUnique default values -- not accidentally :) 
insert into NullAndUnique (name) values ('Joel') 
insert into NullAndUnique (name) values ('Joel') -- Boom! 

utilizza fondamentalmente la id quando il name è nullo. Il + '_' è per evitare casi in cui il nome potrebbe essere numerico, ad esempio 1, che potrebbe entrare in collisione con id.

+1

Davvero? Da MSDN: "Le colonne utilizzate in un indice univoco devono essere impostate su NOT NULL, poiché più valori nulli vengono considerati duplicati quando viene creato un indice univoco." – Arthur

+6

No, SQL Server * NON * consente più valori null in una colonna coperta da un vincolo univoco. Lo standard SQL specifica che questo dovrebbe essere consentito (poiché NULL non è uguale a se stesso), ma Microsoft SQL Server non ha seguito lo standard su questo punto. –

0

L'altro metodo per applicare regole condizionali che un indice univoco o un vincolo di controllo non possono gestire è utilizzare un trigger.

1

Oracle fa. Una chiave completamente nullo non viene indicizzata da un albero B nell'indice in Oracle e Oracle utilizza gli indici B per applicare vincoli univoci.

Supponendo che si volesse la versione id_colonna basato sul ACTIVE_FLAG essere impostato su 1:

CREATE UNIQUE INDEX idx_versioning_id ON mytable 
    (CASE active_flag WHEN 0 THEN NULL ELSE active_flag END, 
    CASE active_flag WHEN 0 THEN NULL ELSE id_column END); 
2

Nel caso di descrizioni che non sono ancora completate, non avrei quelli nella stessa tabella della finalizzato descrizioni. La tabella finale avrebbe quindi un indice univoco o una chiave primaria nella descrizione.

Nel caso di attivo/inattivo, di nuovo potrei avere tabelle separate come si faceva con una tabella "archivio" o "cronologia", ma un altro modo possibile per farlo in MS SQL Server è almeno attraverso l'uso di una vista indicizzata:

CREATE TABLE Test_Conditionally_Unique 
(
    my_id INT NOT NULL, 
    active BIT NOT NULL DEFAULT 0 
) 
GO 
CREATE VIEW dbo.Test_Conditionally_Unique_View 
WITH SCHEMABINDING 
AS 
    SELECT 
     my_id 
    FROM 
     dbo.Test_Conditionally_Unique 
    WHERE 
     active = 1 
GO 
CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id) 
GO 

INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (1, 1) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 0) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 1) 
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active) 
VALUES (2, 1) -- This insert will fail 

È possibile utilizzare lo stesso metodo anche per le descrizioni NULL/Valued.

+0

Grazie, questo funziona nel 2005, e in realtà stavo solo facendo un giro con le viste indicizzate oggi su un problema diverso. Questa è un'applicazione davvero fantastica! –

0

Non sono completamente a conoscenza dell'uso previsto o delle tabelle, ma è possibile provare a utilizzare una relazione uno a uno. Suddividere questa colonna "a volte" unica in una nuova tabella, creare l'indice UNIQUE su quella colonna nella nuova tabella e FK tornare alla tabella originale utilizzando le tabelle originali PK. Solo una riga in questa nuova tabella quando si suppone che i dati "univoci" esistano.

Stagioni precedenti:

TableA 
ID pk 
Col1 sometimes unique 
Col... 

nuove tabelle:

TableA 
ID 
Col... 

TableB 
ID PK, FK to TableA.ID 
Col1 unique index 
Problemi correlati