Por que uma restrição UNIQUE permite apenas um NULL?

36

Tecnicamente, NULL = NULL é False, por essa lógica, NULL é igual a qualquer NULL e todos os NULLs são distintos. Isso não significa que todos os NULLs são únicos e um índice exclusivo deve permitir qualquer número de NULLs?

user87166
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Paul White diz GoFundMonica

Respostas:

52

Por que funciona dessa maneira? Desde quando alguém tomou uma decisão de design sem saber ou se importar com o que o padrão diz (afinal, temos todos os tipos de comportamentos estranhos com NULLs e podemos coagir comportamentos diferentes à vontade). Essa decisão determinou que, neste caso NULL = NULL,.

Não foi uma decisão muito inteligente. O que eles deveriam ter feito é fazer com que o comportamento padrão adira ao padrão ANSI e, se eles realmente desejam esse comportamento peculiar, permitam-no através de uma opção DDL como WITH CONSIDER_NULLS_EQUALou WITH ALLOW_ONLY_ONE_NULL.

Obviamente, retrospectiva é 20/20.

E agora temos uma solução alternativa, mesmo que não seja a mais limpa ou a mais intuitiva.

Você pode obter o comportamento ANSI adequado no SQL Server 2008 e acima, criando um índice filtrado exclusivo.

CREATE UNIQUE INDEX foo ON dbo.bar(key) WHERE key IS NOT NULL;

Isso permite mais de um NULLvalor, porque essas linhas são completamente deixadas de fora da verificação duplicada. Como um bônus adicional, isso acabaria sendo um índice menor do que aquele que consistia em toda a tabela se vários NULLs fossem permitidos (especialmente quando não é a única coluna no índice, possui INCLUDEcolunas, etc.). No entanto, convém conhecer algumas das outras limitações dos índices filtrados:

Aaron Bertrand
fonte
8

Corrigir. A implementação de uma restrição ou índice exclusivo no servidor sql permite um e apenas um NULL. Também corrija que isso tecnicamente não se encaixa na definição de NULL, mas é uma daquelas coisas que eles fizeram para torná-lo mais útil, mesmo que não seja "tecnicamente" correto. Observe que uma PRIMARY KEY (também um índice exclusivo) não permite NULLs (é claro).

Kenneth Fisher
fonte
11
Esse tecnicismo (do SQL Server) também não se encaixa no padrão SQL. Há um item do Connect de 7 anos sobre esse problema.
ypercubeᵀᴹ
@ypercube True. É por isso que eu disse que era apenas a implementação e realmente não se encaixa na definição de NULL. Eu não tinha pensado sobre o índice exclusivo filtrada (embora eu usei-o para outras coisas.)
Kenneth Fisher
3

Primeiro - pare de usar a frase "Valor nulo", isso apenas o desviará. Em vez disso, use a frase "marcador nulo" - um marcador em uma coluna indicando que o valor real nessa coluna está ausente ou não é aplicável (mas observe que o marcador não diz qual dessas opções é realmente o caso¹).

Agora, imagine o seguinte (onde o banco de dados não possui conhecimento completo da situação modelada).

Situation          Database

ID   Code          ID   Code
--   -----         --   -----
1    A             1    A
2    B             2    (null)
3    C             3    C
4    B             4    (null)

A regra de integridade que estamos modelando é "o código deve ser único". A situação do mundo real viola isso; portanto, o banco de dados não deve permitir que os itens 2 e 4 estejam na tabela ao mesmo tempo.

A abordagem mais segura e menos flexível seria desabilitar marcadores nulos no campo Código, para que não haja possibilidade de dados inconsistentes. A abordagem mais flexível seria permitir vários marcadores nulos e se preocupar com a exclusividade quando os valores são inseridos.

Os programadores da Sybase adotaram a abordagem um tanto segura e pouco flexível de permitir apenas um marcador nulo na tabela - algo que os comentaristas reclamam desde então. A Microsoft continuou esse comportamento, acho que para compatibilidade com versões anteriores.


¹ Tenho certeza de que li em algum lugar que o Codd considerou implementar dois marcadores nulos - um para desconhecido e outro para inaplicável - mas o rejeitou, mas não consigo encontrar a referência. Estou me lembrando corretamente?

PS Minha citação favorita sobre null: Louis Davidson, "Design de banco de dados profissional do SQL Server 2000", Wrox Press, 2001, página 52. "Resumiu em uma única frase: NULL é mau".

Andarilho de Pedra Verde
fonte
11
Permitir que um single nulltambém não atinja esse objetivo. Como o valor ausente pode ser o mesmo que o valor em uma das outras linhas.
Martin Smith
11
O que o @MartinSmith disse. E se você tiver uma restrição de cheque CHECK (Value IN ('A','B','C','D'))? Em seguida, a implementação do SQL Server e o padrão SQL permitem que a tabela tenha 5 linhas (uma linha para cada valor mais 1 com NULL.) Então, sem dúvida, embora o banco de dados seja consistente com suas restrições, não é consistente com a intenção do designer de a tabela deve ter no máximo 4 linhas. Não há valor que o NULL possa ser alterado para não violar uma restrição, a menos que uma ou mais linhas sejam excluídas.
ypercubeᵀᴹ
11
O fato de o padrão permitir 6 ou mesmo 106 linhas em vez de 5 não altera que ambos falhem de alguma maneira nesse cenário.
usar o seguinte comando
@ Martin Smith, pode, mas, novamente, pode não - o servidor de banco de dados não pode saber, para não arriscar e seguir a rota segura. Foi isso que os programadores da Sybase (eu presumo) decidiram, causando aborrecimento desde então (pelo menos no Inside SQL Server 6.5, o livro mais antigo da minha estante, onde Ron Soukup faz o mesmo comentário que Aaron Bertrand faz em sua resposta) . Eu acho que poderia ser pior - eles poderiam não ter exigido marcadores nulos. :-)
Greenstone Walker
2
@GreenstoneWalker - Não segue a rota "segura". Pressupõe que o valor ausente não entre em conflito. CREATE TABLE #T(A INT NULL UNIQUE);INSERT INTO #T VALUES (1),(NULL);UPDATE #T SET A = 1 WHERE A IS NULL;irá gerar um erro. De acordo com sua teoria das motivações do projeto, deveria ter impedido a inserção NULLno primeiro caso - porque o conhecimento incompleto significa que não há garantia de que o valor seja diferente.
Martin Smith
2

Isso pode não ser tecnicamente preciso, mas filosoficamente me ajuda a dormir à noite ...

Como vários outros já disseram ou fizeram alusão, se você pensa em NULL como desconhecido, não pode determinar se um valor NULL é de fato igual a outro valor NULL. Pensando dessa maneira, a expressão NULL == NULL deve ser avaliada como NULL, ou seja, desconhecida.

Uma restrição exclusiva precisaria de um valor definitivo para a comparação dos valores da coluna. Em outras palavras, ao comparar um único valor da coluna com qualquer outro valor da coluna usando o operador de igualdade, ele deve avaliar como false para ser válido. O desconhecido não é realmente falso, embora seja frequentemente tratado como falso. Dois valores NULL podem ser iguais ou não ... simplesmente não podem ser determinados definitivamente.

Ajuda pensar em uma restrição única como valores restritivos que podem ser determinados como distintos um do outro. O que quero dizer com isso é se você executar um SELECT que se parece com isso:

SELECT * from dbo.table1 WHERE ColumnWithUniqueContraint="some value"

A maioria das pessoas esperaria um resultado, uma vez que existe uma restrição única. Se você permitisse vários valores NULL em ColumnWithUniqueConstraint, seria impossível selecionar uma única linha distinta da tabela usando NULL como o valor comparado.

Dado isso, acredito que, independentemente de ser implementado com precisão ou não com relação à definição de NULL, é definitivamente muito mais prático na maioria das situações do que permitir vários valores NULL.

EricJ
fonte
Seu Select fornecerá 1 resultado, quando houver uma restrição Exclusiva (em qualquer implementação, não apenas no SQL-Server). O que você quer com isso?
ypercubeᵀᴹ
-3

Um dos principais objetivos de uma UNIQUErestrição é impedir registros duplicados. Se for necessário ter uma tabela na qual possa haver vários registros em que um valor seja "desconhecido", mas não for permitido que dois registros tenham o mesmo valor "conhecido", os valores desconhecidos deverão receber identificadores artificiais exclusivos antes de serem adicionado à tabela.

Existem alguns casos raros em que uma coluna que possui uma UNIQUErestrição e contém um único valor nulo; por exemplo, se uma tabela contiver um mapeamento entre os valores da coluna e as descrições de texto localizadas, uma linha para NULLpermitiria definir a descrição que deve aparecer quando essa coluna em alguma outra tabela estiver NULL. O comportamento de NULLpermite esse caso de uso.

Caso contrário, não vejo base para um banco de dados com UNIQUErestrição em nenhuma coluna para permitir a existência de muitos registros idênticos, mas não vejo como impedir isso ao permitir vários registros cujos valores-chave não são distinguíveis. Declarar que NULLnão é igual a si mesmo não tornará os NULLvalores distinguíveis um do outro.

supercat
fonte
3
Identificadores únicos artificiais são uma piada, desculpe. Como você fará isso para um VIN? Se você não sabe o que é, por que inventar algo? Apenas para ocupar espaço em disco extra? Parece absurdo solucionar outro problema (como não querer escrever o aplicativo de maneira que ele lide com NULLs). Se você absolutamente precisar saber por que algo é NULL (existe, mas é desconhecido vs. sabe que não existe vs. não sabe ou não se importa se existe, por exemplo), adicione algum tipo de coluna de status. Os tokens apenas levam a códigos complicados para lidar com eles.
Aaron Bertrand
Depende muito do objetivo da restrição de exclusividade. Se um campo for usado como identificador, ele não deve ser nulo. Nos casos (como nos VINs) em que as regras de negócios sugerem que quando um item aparece duas vezes, um deles deve estar errado, mas alguns itens podem ser "não sei", uma restrição de exclusividade não parece a abordagem adequada. Se alguém possui um veículo com um VIN conhecido e entra em conflito com outro no banco de dados, pode-se saber que pelo menos um dos VINs está errado, mas seria melhor que o banco de dados relate o valor acreditado para ambos os registros do que o palpite. esse está certo.
Supercat
@AaronBertrand: Existem alguns casos em que um campo exclusivo-se-não-nulo possivelmente nulo precisaria ser uma chave substituta não poderia ser estabelecida antes do preenchimento do campo (por exemplo, "ID do cônjuge"), mas em situações como que uma restrição "única" seria insuficiente; seria necessário que, se X.Spouse não for nulo, X.Spouse.Spouse = X. Aliás, algo como "cônjuge" também pode ser tratado dizendo que o registro para uma pessoa solteira não deve ter "NULL" como cônjuge, mas sim seu próprio ID, nesse caso a regra X.spouse.spouse = X poderia aplicar a todos.
Supercat