Como criar um índice exclusivo em uma coluna NULL?

101

Estou usando o SQL Server 2005. Quero restringir os valores em uma coluna para serem exclusivos, permitindo NULLS.

Minha solução atual envolve um índice exclusivo em uma exibição como esta:

CREATE VIEW vw_unq WITH SCHEMABINDING AS
    SELECT Column1
      FROM MyTable
     WHERE Column1 IS NOT NULL

CREATE UNIQUE CLUSTERED INDEX unq_idx ON vw_unq (Column1)

Alguma ideia melhor?

Nuno G
fonte
16
sem chance de usar o sql 2008? você pode criar um índice filtrado usando 'where'
Simon_Weaver
3
Você não quis dizer único, permitindo NULLs , parece que quis dizer único, mas incluindo vários NULLs . Caso contrário, NULL é indexado como qualquer outro valor e a restrição de exclusividade funciona conforme o esperado - apenas não de acordo com os padrões SQL, como @pst mencionado em um comentário abaixo.
Suncat2000

Respostas:

26

Tenho certeza de que você não pode fazer isso, pois viola o propósito dos únicos.

No entanto, essa pessoa parece ter uma solução decente: http://sqlservercodebook.blogspot.com/2008/04/multiple-null-values-in-unique-index-in.html

Willasaywhat
fonte
2
Parece que o conteúdo do link que você forneceu foi, na verdade, (parcialmente) copiado sem atribuição a partir daqui: decipherinfosys.wordpress.com/2007/11/30/…
Tom Juergens
77
Não concordo que "viole o propósito dos únicos" - NULL é um valor especial em SQL (semelhante em muitos aspectos a NaN) e precisa ser tratado de acordo. Na verdade, é uma falha no SQL Server honrar várias especificações SQL: aqui está um link para uma solicitação de "implementação correta" pelo que vale a pena: connect.microsoft.com/SQLServer/feedback/details/299229/… .
5
para referência em 2008, você pode fazer CREATE UNIQUE INDEX foo ON dbo.bar (tecla) WHERE key IS NOT NULL;
niico
2
Eu também discordo com "viola o propósito dos únicos", NULL não é igual a NULL, então você deve ser capaz de criar um índice único em uma coluna anulável e inserir vários nulos.
Wodzu
105

Usando o SQL Server 2008, você pode criar um índice filtrado: http://msdn.microsoft.com/en-us/library/cc280372.aspx . (Vejo que Simon adicionou isso como um comentário, mas pensei que merecia sua própria resposta, pois o comentário é facilmente esquecido.)

Outra opção é um gatilho para verificar a exclusividade, mas isso pode afetar o desempenho.

Phil Haselden
fonte
84
create unique index UIX on MyTable (Column1) where Column1 is not null
Jørn Schou-Rode
1
Nota: atualmente o SQL Server Management Studio não parece saber como criar tais índices, então se você modificar a tabela posteriormente, ela ficará confusa e tentará
eliminá-
3
Parece que a Microsoft atualizou o SSMS para oferecer suporte a isso. Eu tenho SSMS 10.50.1617 e na caixa de diálogo Propriedades do índice, você pode selecionar a página Filtro para editar o filtro. por exemplo, "([Coluna1] NÃO É NULO)"
Phil Haselden
5
Permitir vários nulos em um índice e filtrar nulos de um índice são coisas diferentes. Na verdade, a filtragem de um índice exclui registros do índice, enquanto as outras soluções transformam o nulo em um valor exclusivo útil. Esteja ciente da diferença.
Suncat2000
Se você estiver usando procedimentos armazenados em uma tabela com um índice filtrado como esse, certifique-se de que ANSI_NULLSestá ON, caso contrário, você obterá um erro ao tentar inserir dados.
Arne,
71

O truque da coluna calculada é amplamente conhecido como "nullbuster"; minhas notas creditam a Steve Kass:

CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X  int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)
um dia quando
fonte
Isso parece um truque legal. Estranhamente, a busca por nullbuster não traz muita coisa. Estou me perguntando se isso será útil para acelerar as pesquisas também - em vez de uma coluna computada de apenas 1 e 0 para nulo ou não, se usar o PK dá ao índice algo mais para trabalhar? Vou testar neste fim de semana em uma mesa grande e ver.
David Storfer
@DavidStorfer, você não pode fazer isso porque pode haver uma colisão entre os IDs das duas tabelas diferentes.
user393274
Melhoria: ISNULL (X, CONVERT (VARCHAR (10), pk))
Faiz
5
@Faiz: A melhoria está nos olhos de quem vê. Eu prefiro a aparência do original.
quando
@NunoG, esta deve ser a resposta aceita, pois fornece uma boa solução compatível com seus requisitos, ao invés de apenas vincular um site externo que pode desaparecer.
Frédéric
-3

Estritamente falando, uma coluna anulável exclusiva (ou conjunto de colunas) pode ser NULL (ou um registro de NULLs) apenas uma vez, visto que ter o mesmo valor (e isso inclui NULL) mais de uma obviamente viola a restrição exclusiva.

No entanto, isso não significa que o conceito de "colunas anuláveis ​​exclusivas" seja válido; para realmente implementá-lo em qualquer banco de dados relacional, só temos que ter em mente que este tipo de banco de dados deve ser normalizado para funcionar corretamente, e a normalização geralmente envolve a adição de várias tabelas extras (não-entidades) para estabelecer relacionamentos entre as entidades .

Vamos trabalhar um exemplo básico considerando apenas uma "coluna anulável única", é fácil expandi-la para mais colunas.

Suponha que as informações representadas por uma tabela como esta:

create table the_entity_incorrect
(
  id integer,
  uniqnull integer null, /* we want this to be "unique and nullable" */
  primary key (id)
);

Podemos fazer isso separando uniqnull e adicionando uma segunda tabela para estabelecer uma relação entre valores uniqnull e the_entity (em vez de ter uniqnull "dentro" da_entidade):

create table the_entity
(
  id integer,
  primary key(id)
);

create table the_relation
(
  the_entity_id integer not null,
  uniqnull integer not null,

  unique(the_entity_id),
  unique(uniqnull),
  /* primary key can be both or either of the_entity_id or uniqnull */
  primary key (the_entity_id, uniqnull), 
  foreign key (the_entity_id) references the_entity(id)
);

Para associar um valor de uniqnull a uma linha na entidade, precisamos também adicionar uma linha na relação.

Para linhas em the_entity onde nenhum valor uniqnull está associado (ou seja, para aqueles que colocaríamos NULL em the_entity_incorrect), simplesmente não adicionamos uma linha em the_relation.

Observe que os valores para uniqnull serão únicos para toda a relação, e também observe que para cada valor na entidade pode haver no máximo um valor na relação, uma vez que as chaves primária e externa obrigam isso.

Então, se um valor de 5 para uniqnull deve ser associado a um id the_entity de 3, precisamos:

start transaction;
insert into the_entity (id) values (3); 
insert into the_relation (the_entity_id, uniqnull) values (3, 5);
commit;

E, se um valor de id de 10 para the_entity não tiver uma contraparte única, fazemos apenas:

start transaction;
insert into the_entity (id) values (10); 
commit;

Para desnormalizar essas informações e obter os dados que uma tabela como the_entity_incorrect manteria, precisamos:

select
  id, uniqnull
from
  the_entity left outer join the_relation
on
  the_entity.id = the_relation.the_entity_id
;

O operador "left outer join" garante que todas as linhas de the_entity apareçam no resultado, colocando NULL na coluna uniqnull quando nenhuma coluna correspondente estiver presente na relação.

Lembre-se de que qualquer esforço despendido por alguns dias (ou semanas ou meses) no projeto de um banco de dados bem normalizado (e as visualizações e procedimentos desnormalizantes correspondentes) poupará anos (ou décadas) de dor e desperdício de recursos.

Roy
fonte
6
Como já declarado no comentário da resposta aceita com cinquenta votos positivos, deve ser suportado pelo MS Sql Server para ter vários nulos em colunas indexadas como exclusivas. É uma falha na implementação dos padrões SQL não permitir isso. Nulo não é um valor, nulo não é igual a nulo, essa é uma regra SQL básica desde anos. Portanto, sua primeira frase está errada e a maioria dos leitores não se preocupará em continuar lendo.
Frédéric