SQL Server 2008 e superior
Basta filtrar um índice exclusivo:
CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;
Nas versões inferiores, uma exibição materializada ainda não é necessária
Para o SQL Server 2005 e versões anteriores, você pode fazer isso sem uma exibição. Acabei de adicionar uma restrição única, como você está pedindo em uma das minhas tabelas. Dado que desejo exclusividade na coluna SamAccountName
, mas desejo permitir vários NULLs, usei uma coluna materializada em vez de uma exibição materializada:
ALTER TABLE dbo.Party ADD SamAccountNameUnique
AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
UNIQUE (SamAccountNameUnique)
Você simplesmente precisa colocar algo na coluna computada que será garantida exclusiva em toda a tabela quando a coluna exclusiva desejada real for NULL. Nesse caso, PartyID
é uma coluna de identidade e ser numérico nunca corresponderá a nenhum SamAccountName
, portanto funcionou para mim. Você pode tentar seu próprio método - certifique-se de entender o domínio de seus dados para que não haja possibilidade de interseção com dados reais. Isso pode ser tão simples quanto adicionar um caractere diferenciador como este:
Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))
Mesmo que PartyID
um dia se torne não numérico e possa coincidir com um SamAccountName
, agora não importa.
Observe que a presença de um índice incluindo a coluna computada faz com que cada resultado de expressão seja salvo no disco com os outros dados da tabela, o que ocupa espaço em disco adicional.
Observe que, se você não quiser um índice, ainda poderá salvar a CPU, fazendo com que a expressão seja pré-calculada em disco, adicionando a palavra PERSISTED
- chave ao final da definição da expressão da coluna.
No SQL Server 2008 e versões posteriores, use definitivamente a solução filtrada, se possível!
Controvérsia
Observe que alguns profissionais de banco de dados verão isso como um caso de "NULLs substitutos", que definitivamente apresentam problemas (principalmente devido a problemas ao tentar determinar quando algo é um valor real ou um valor substituto para a falta de dados ; também pode haver problemas com o número de valores substitutos que não sejam NULL multiplicando-se como loucos).
No entanto, acredito que este caso seja diferente. A coluna computada que estou adicionando nunca será usada para determinar nada. Ele não tem significado próprio e não codifica nenhuma informação que ainda não foi encontrada separadamente em outras colunas definidas corretamente. Nunca deve ser selecionado ou usado.
Então, minha história é que esse não é um NULL substituto, e estou cumprindo! Como na verdade, não queremos que o valor não-NULL seja para outra finalidade além de enganar o UNIQUE
índice para ignorar NULLs, nosso caso de uso não apresenta nenhum dos problemas que surgem com a criação normal de NULL substituta.
Tudo isso dito, não tenho nenhum problema em usar uma exibição indexada - mas isso traz alguns problemas, como a exigência de uso SCHEMABINDING
. Divirta-se adicionando uma nova coluna à sua tabela base (você precisará, no mínimo, soltar o índice e, em seguida, soltar a visualização ou alterar a visualização para não ser vinculada ao esquema). Consulte a lista completa (longa) de requisitos para criar uma exibição indexada no SQL Server (2005) (também versões posteriores), (2000) .
Atualizar
Se sua coluna for numérica, pode haver o desafio de garantir que a restrição exclusiva usando Coalesce
não resulte em colisões. Nesse caso, existem algumas opções. Pode-se usar um número negativo, colocar os "NULL substitutos" somente na faixa negativa e os "valores reais" somente na faixa positiva. Como alternativa, o seguinte padrão pode ser usado. Na tabela Issue
(onde IssueID
está o PRIMARY KEY
), pode ou não haver um TicketID
, mas se houver, ele deve ser único.
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 o IssueID 1 tiver o ticket 123, a UNIQUE
restrição estará nos valores (123, NULL). Se o IssueID 2 não tiver um ticket, ele estará ativado (NULL, 2). Alguns pensam que mostrarão que essa restrição não pode ser duplicada para nenhuma linha da tabela e ainda permite vários NULLs.