SQL Server 2008 e versioni successive
Basta filtrare un indice univoco:
CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;
Nelle versioni inferiori, una vista materializzata non è ancora richiesta
Per SQL Server 2005 e versioni precedenti, è possibile farlo senza una vista. Ho appena aggiunto un vincolo unico come quello che stai chiedendo a uno dei miei tavoli. Dato che voglio l'univocità nella colonna SamAccountName
, ma voglio consentire più NULL, ho usato una colonna materializzata piuttosto che una vista materializzata:
ALTER TABLE dbo.Party ADD SamAccountNameUnique
AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
UNIQUE (SamAccountNameUnique)
Devi semplicemente inserire qualcosa nella colonna calcolata che sarà garantita unica nell'intera tabella quando la colonna unica desiderata effettiva è NULL. In questo caso, PartyID
è una colonna identità ed essere numerico non corrisponderà mai a nessuno SamAccountName
, quindi ha funzionato per me. Puoi provare il tuo metodo: assicurati di comprendere il dominio dei tuoi dati in modo che non vi sia alcuna possibilità di intersezione con dati reali. Potrebbe essere semplice come anteporre un personaggio differenziatore come questo:
Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))
Anche se PartyID
un giorno diventasse non numerico e potesse coincidere con un SamAccountName
, ora non importa.
Si noti che la presenza di un indice che include la colonna calcolata comporta implicitamente il salvataggio di ogni risultato di espressione su disco con gli altri dati nella tabella, che occupa spazio su disco aggiuntivo.
Si noti che se non si desidera un indice, è comunque possibile salvare la CPU eseguendo il calcolo preliminare dell'espressione sul disco aggiungendo la parola chiave PERSISTED
alla fine della definizione dell'espressione di colonna.
In SQL Server 2008 e versioni successive, utilizza sicuramente la soluzione filtrata, se possibile!
Controversia
Si noti che alcuni professionisti del database vedranno questo come un caso di "NULL surrogati", che sicuramente hanno problemi (principalmente a causa di problemi nel tentativo di determinare quando qualcosa è un valore reale o un valore surrogato per i dati mancanti ; possono esserci anche problemi con il numero di valori surrogati non NULL che si moltiplicano come matti).
Tuttavia, credo che questo caso sia diverso. La colonna calcolata che sto aggiungendo non verrà mai utilizzata per determinare nulla. Non ha alcun significato e non codifica informazioni che non sono già state trovate separatamente in altre colonne correttamente definite. Non dovrebbe mai essere selezionato o utilizzato.
Quindi, la mia storia è che questo non è un NULL surrogato e mi sto attenendo ad esso! Poiché in realtà non vogliamo il valore non NULL per scopi diversi dall'inganno l' UNIQUE
indice a ignorare i NULL, il nostro caso d'uso non presenta nessuno dei problemi che sorgono con la normale creazione NULL surrogata.
Detto questo, non ho alcun problema con l'utilizzo di una vista indicizzata, ma comporta alcuni problemi come il requisito dell'uso SCHEMABINDING
. Divertiti ad aggiungere una nuova colonna alla tabella di base (dovrai almeno eliminare l'indice, quindi rilasciare la vista o modificare la vista per non essere associato allo schema). Vedere l' elenco completo (lungo) dei requisiti per la creazione di una vista indicizzata in SQL Server (2005) (anche versioni successive), (2000) .
Aggiornare
Se la colonna è numerica, potrebbe esserci la sfida di garantire che il vincolo univoco utilizzato Coalesce
non provochi collisioni. In tal caso, ci sono alcune opzioni. Uno potrebbe essere quello di usare un numero negativo, per mettere i "NULL surrogati" solo nell'intervallo negativo, e i "valori reali" solo nell'intervallo positivo. In alternativa, è possibile utilizzare il modello seguente. Nella tabella Issue
(dove si IssueID
trova PRIMARY KEY
), potrebbe esserci o meno un TicketID
, ma se ce n'è uno, deve essere univoco.
ALTER TABLE dbo.Issue ADD TicketUnique
AS (CASE WHEN TicketID IS NULL THEN IssueID END);
ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull
UNIQUE (TicketID, TicketUnique);
Se IssueID 1 ha il ticket 123, il UNIQUE
vincolo sarà sui valori (123, NULL). Se IssueID 2 non ha un ticket, sarà attivo (NULL, 2). Qualche pensiero mostrerà che questo vincolo non può essere duplicato per nessuna riga della tabella e consente comunque NULL multipli.