Por que não devemos permitir NULLs?

125

Lembro-me de ler este artigo sobre o design de banco de dados e também lembro que você deveria ter propriedades de campo NOT NULL. Não me lembro por que esse foi o caso.

Tudo o que consigo pensar é que, como desenvolvedor de aplicativos, você não precisaria testar NULL e um possível valor inexistente de dados (por exemplo, uma string vazia para strings).

Mas o que você faz no caso de datas, data e hora e hora (SQL Server 2008)? Você precisaria usar uma data histórica ou de fundo do poço.

Alguma idéia sobre isso?

Thomas Stringer
fonte
4
Esta resposta tem uma visão geral sobre o uso de NULL dba.stackexchange.com/questions/5176/…
Derek Downey
10
Realmente? Por que o RDBMS nos permite usar NULL, se não devemos usá-los? Não há nada errado com o NULL, desde que você saiba como lidar com eles.
precisa saber é o seguinte
3
Isso foi uma modelagem de dados de BI? Você geralmente não deve permitir nulos nas tabelas de fatos ... caso contrário, os nulos são seus amigos quando usados ​​corretamente. =)
sam yi
2
@ Fr0zenFyr, só porque um RDBMS nos permite fazer algo, não é necessariamente uma boa ideia fazê-lo. Nada nos obriga a declarar uma chave primária ou uma chave única em uma tabela, mas com poucas exceções, de qualquer maneira.
Lennart
3
Eu acho que um tratamento completo desse assunto teria que fazer referência ao requisito original de Codd de que um RDBMS deve ter uma maneira sistemática de tratar dados ausentes. No mundo real, há situações em que um local para dados é criado, mas não há dados para colocar nele. O Data Architect precisa apresentar alguma resposta para isso, se envolve design de banco de dados, programação de aplicativos ou ambos. O SQL NULL é menos que perfeito para atender a esse requisito, mas é melhor que nada.
Walter Mitty

Respostas:

230

Acho que a pergunta está mal formulada, pois a redação implica que você já decidiu que os NULLs são ruins. Talvez você quis dizer "Devemos permitir NULLs?"

Enfim, aqui está a minha opinião: acho que os NULLs são uma coisa boa. Quando você começa a impedir NULLs apenas porque "NULLs são ruins" ou "NULLs são difíceis", você começa a criar dados. Por exemplo, e se você não souber minha data de nascimento? O que você vai colocar na coluna até saber? Se você é como um monte de gente anti-NULL, vai entrar em 1900-01-01. Agora vou ser colocado na enfermaria geriátrica e provavelmente receber uma ligação da minha estação de notícias local parabenizando-me por minha longa vida, me perguntando meus segredos para viver uma vida tão longa, etc.

Se uma linha puder ser inserida onde é possível que você não saiba o valor de uma coluna, acho que NULL faz muito mais sentido do que escolher algum valor de token arbitrário para representar o fato de que é desconhecido - um valor que outros irão você já deve saber, fazer engenharia reversa ou pedir para descobrir o que isso significa.

Porém, existe um equilíbrio - nem todas as colunas do seu modelo de dados devem ser anuláveis. Geralmente, existem campos opcionais em um formulário ou informações que, de outra forma, não são coletadas no momento em que a linha é criada. Mas isso não significa que você pode adiar o preenchimento de todos os dados. :-)

Além disso, a capacidade de usar NULL pode ser limitada por requisitos cruciais na vida real. No campo da medicina, por exemplo, pode ser uma questão de vida ou morte saber por que um valor é desconhecido. A frequência cardíaca é NULL porque não havia pulso ou porque ainda não o medimos? Nesse caso, podemos colocar NULL na coluna da frequência cardíaca e ter notas ou uma coluna diferente com um motivo NULL-porque?

Não tenha medo dos NULLs, mas esteja disposto a aprender ou ditar quando e onde eles devem ser usados ​​e quando e onde não devem.

Aaron Bertrand
fonte
3
"algum valor simbólico arbitrário para representar o fato de que não se sabe" o que é conhecido como um valor de sentinela
Alexander
4
Mas o que impede você de criar uma tabela separada birth_dateonde você armazena datas de nascimento? Se a data de nascimento for desconhecida, simplesmente não insira a data de nascimento birth_date. Nulos são desastre.
Eldar Agalarov
6
@EldarAgalarov Parece o raciocínio de Trump ("desastre", por quê? Como? Para quem? Sua opinião de que algo é um "desastre" não é assim). De qualquer forma, a data de nascimento é apenas um exemplo. Se você possui funcionários, membros ou clientes com 15 colunas potencialmente anuláveis, criará 15 tabelas secundárias? E se você tiver 50? E se a sua tabela de fatos DW tiver 500? A manutenção para manter NULLs grandes e assustadores fora do seu banco de dados se torna 10x tão ruim quanto qualquer "desastre" que você tem medo ...
Aaron Bertrand
3
@AaronBertrand, se sua tabela tiver 15 colunas potencialmente anuláveis, cheira muito mal ^^ Não que um grande número de colunas seja inerentemente ruim, mas pode indicar um design incorreto OU desnormalização necessária. Mas isso levantará questões.
programasths
2
@Wildcard Então, você nunca viu pessoas armazenando 1900-01-01para evitar um valor NULL de data / hora? OK então. Além disso, NULL = desconhecido e desconhecido = falso. Não sei ao certo que problemas isso pode causar, além de pessoas que não nasceram sabendo disso (como se não tivessem nascido sabendo muitas coisas inerentes a um RDBMS complexo). Mais uma vez, agitando as mãos e dizendo "Problema! Desastre!" não faz assim.
Aaron Bertrand
57

Os motivos estabelecidos são:

  • NULL não é um valor e, portanto, não possui um tipo de dados intrínseco. Os nulos precisam de tratamento especial em todo o lugar, quando o código que depende de tipos reais também pode receber o NULL não digitado.

  • NULL quebra a lógica de dois valores (familiar Verdadeiro ou Falso) e requer uma lógica de três valores. Isso é muito mais complexo de implementar corretamente, e certamente é pouco compreendido pela maioria dos DBAs e praticamente todos os que não são DBAs. Como conseqüência, ele convida positivamente muitos erros sutis no aplicativo.

  • O significado semântico de qualquer NULL específico é deixado para o aplicativo , diferentemente dos valores reais.

    Semânticas como “não aplicável” e “desconhecido” e “sentinela” são comuns, e existem outras também. Eles são freqüentemente usados ​​simultaneamente no mesmo banco de dados, mesmo na mesma relação; e, é claro, significados inexplicáveis, indistinguíveis e incompatíveis .

  • Eles não são necessários para bancos de dados relacionais , conforme argumentado em "Como lidar com informações ausentes sem nulos" . Normalização adicional é um primeiro passo óbvio para tentar livrar uma tabela de NULLs.

Isso não significa que NULL nunca deve ser permitido. Ele se argumentar que há muitas boas razões para não permitir NULL sempre que possível.

Significativamente, ele argumenta que é muito difícil - através de um melhor design de esquema, melhores mecanismos de banco de dados e ainda melhores linguagens de banco de dados - tornar possível evitar NULL com mais frequência.

Fabian Pascal responde a uma série de argumentos, em "Nulls Nullified" .

nariz grande
fonte
3
Seu link para "Como lidar com informações ausentes sem nulos" mostra bastante por que não podemos fazer sem nulos: Várias sugestões seriam impossíveis de implementar de maneira racional nos principais RDBMSs como estão atualmente.
Jack Douglas
7
Jack: Certo, mas “as implementações atuais não podem fazê-lo” não é um argumento para o :-) status quo
Bignose
17
É como dizer que não devemos voar porque os aviões não são perfeitos?
Aaron Bertrand
11
Não, está dizendo que os fornecedores devem parar de invocar desculpas para nulos que poderiam ter sido válidos há quarenta anos, mas que sobreviveram por muito tempo a um período razoável de retenção. Os tempos de E / S não são mais da ordem de 80ms. Ciclos únicos de CPU não estão mais na ordem de magnitude de microssegundos. Os limites de memória não estão mais na ordem de magnitude de alguns Megs. Ao contrário de quarenta anos atrás, as velocidades e capacidades de hardware necessárias para trabalhar sem valores nulos agora existem com o custo não sendo proibitivo. Ele está dizendo que é hora de seguir em frente.
Erwin Smout
2
O link "confusão nula" está morto.
Jpmc26
32

Eu discordo, os nulos são um elemento essencial do design do banco de dados. A alternativa, como você aludiu também, seria uma proliferação de valores conhecidos para representar os desaparecidos ou os desconhecidos. O problema está no fato de o nulo ser tão amplamente incompreendido e, como resultado, ser usado de forma inadequada.

Segundo o IIRC, Codd sugeriu que a implementação atual de null (significando não presente / ausente) poderia ser melhorada com dois marcadores nulos em vez de um, "não presente, mas aplicável" e "não presente e não aplicável". Não é possível imaginar como os projetos relacionais seriam melhorados pessoalmente.

Mark Storey-Smith
fonte
2
Sugiro ter um conjunto definido pelo usuário de diferentes tipos de null, e uma lógica de valores múltiplos definido pelo usuário para ir com eles: p
Jack Douglas
13
Essas não são as únicas opções. Você exclui a alternativa de normalização: em vez de colunas que podem ou não ter um valor, use outra tabela que pode ou não ter uma linha correspondente para a primeira tabela. O significado da presença ou ausência de uma fileira está implicado no sentido das mesas, e não há nenhuma especial-invólucro de nulos ou sentinela valores etc.
Bignose
7
A presença de NULL não requer valores especiais de invólucro ou sentinela. Esses são apenas sintomas de como algumas pessoas decidem lidar com NULLs.
Aaron Bertrand
Vale ressaltar que '' é diferente de nulo no PostgreSQL (embora não seja Oracle) e, portanto, fornece um marcador duplo, e você pode usar 0 para colunas numéricas. O problema com 0, porém, é que ele não funciona para chaves estrangeiras.
Chris Travers
13

Deixe-me começar dizendo que não sou um DBA, sou um desenvolvedor de cor e mantenho e atualizo nossos bancos de dados com base em nossas necessidades. Dito isto, tive a mesma pergunta por alguns motivos.

  1. Valores nulos tornam o desenvolvimento mais difícil e propenso a erros.
  2. Valores nulos tornam as consultas, procedimentos armazenados e visualizações mais complexas e propensas a erros.
  3. Valores nulos ocupam espaço (? Bytes com base no comprimento fixo da coluna ou 2 bytes no comprimento variável da coluna).
  4. Valores nulos podem e freqüentemente afetam a indexação e a matemática.

Passo muito tempo vasculhando as respostas, comentários, artigos e conselhos por toda a Internet. Escusado será dizer que a maioria das informações foi a mesma que a resposta de @ AaronBertrand. Por isso, senti a necessidade de responder a essa pergunta.

Em primeiro lugar, quero esclarecer algo para todos os futuros leitores ... Valores nulos representam dados desconhecidos, NÃO dados não utilizados. Se você possui uma tabela de funcionários com um campo de data de término. Um valor nulo na data de término é porque é um campo obrigatório futuro que atualmente é desconhecido. Todo funcionário, ativo ou demitido, em algum momento terá uma data adicionada a esse campo. Essa é, na minha opinião, a única razão para um campo Anulável.

Dito isto, a mesma tabela de funcionários provavelmente conteria algum tipo de dados de autenticação. É comum em um ambiente corporativo que os funcionários sejam listados no banco de dados para RH e contabilidade, mas nem sempre possuem ou precisam de detalhes de autenticação. A maioria das respostas levaria você a acreditar que não há problema em anular esses campos ou, em alguns casos, criar uma conta para eles, mas nunca enviar as credenciais. O primeiro fará com que sua equipe de desenvolvimento escreva um código para verificar se há NULLs e lide com eles de acordo, e o último representa um enorme risco à segurança! Contas que nunca são usadas ainda no sistema apenas aumentam o número de pontos de acesso possíveis para um hacker, além de ocupar um espaço valioso no banco de dados para algo que nunca é usado.

Dadas as informações acima, a melhor maneira de lidar com dados anuláveis ​​que serão utilizados é permitir valores anuláveis. É triste, mas é verdade, e seus desenvolvedores o odiarão por isso. O segundo tipo de dados anuláveis ​​deve ser colocado em uma tabela relacionada (IE: conta, credenciais, etc.) e ter um relacionamento individual. Isso permite que um usuário exista sem credenciais, a menos que seja necessário. Isso remove o risco extra de segurança, o espaço valioso do banco de dados e fornece um banco de dados muito mais limpo.

Abaixo está uma estrutura de tabela muito simplista que mostra a coluna anulável necessária e um relacionamento um para um.

Relacionamento anulável desconhecido e um para um

Sei que estou um pouco atrasado para a festa, já que essa pergunta foi feita anos atrás, mas espero que isso ajude a esclarecer esse assunto e a melhor forma de lidar com ele.

Nicholas Aguirre
fonte
2
Gostaria apenas de alterá-lo para que não exista TerminationDatenos registros dos funcionários, mas tenha uma tabela para a TerminatedEmployeequal os funcionários são movidos para (não copiados) pelo aplicativo quando são finalizados. Obviamente, isso funciona bem com a tabela Conta, porque não haverá uma conta vinculada na TerminatedEmployeetabela. Se você ainda precisar dos números de telefone, eu inverteria as chaves estrangeiras para que as tabelas de funcionário e funcionário encerrado tenham o ID do número de telefone, e não o contrário.
Programster
2
Eu poderia literalmente continuar por dias sobre por que isso seria ruim. Tabelas redundantes, práticas inadequadas de SQL, fazendo com que seus desenvolvedores precisem procurar em dois locais dados de funcionários, problemas com relatórios, problemas com URIs diretos para um funcionário que não existe (foi movido) e a lista continua e assim por diante. É completamente bom ter NULLS para campos que um dia terão um valor; é outra história ter campos que nunca são preenchidos e nunca têm uso. Um número de possíveis problemas e soluções alternativas para fazer esse trabalho não valeria o pequeno problema de verificação de NULL em um campo.
Nicholas Aguirre
1
Discordo. A única coisa redundante é esse campo nulo para a data de término que pode nunca ser preenchido. Os desenvolvedores precisam apenas procurar na tabela apropriada os dados que desejam e podem melhorar o desempenho. Se, por algum motivo, você quiser funcionários demitidos e não, ele será resolvido por uma associação, mas 90% das vezes seu aplicativo provavelmente desejará um ou outro. Eu acho que o layout que eu especifiquei é melhor porque seria impossível ter uma data de término em um funcionário e ele ainda ter uma conta.
Programster
2
Eu não disse dados redundantes, eu disse tabelas redundantes. Além disso, qualquer alteração nas tabelas de funcionários deve chegar às tabelas encerradas; isso torna o aplicativo propenso a erros e dificulta o trabalho do desenvolvedor. Além disso, um campo Data de término será preenchido para quase todos. É um desperdício e problemático criar uma segunda estrutura de tabela idêntica e também mover dados. Não para incluir o teste de cada vez para garantir que os dados da tabela foram movidos e limpos. É uma prática recomendada remover dados de uma tabela, mesmo que apenas para movê-los. Se você está tão preocupado com um único campo que ...
Nicholas Aguirre
1
... que quase sempre será preenchido no tempo e, em seguida, criará uma tabela finalizada com um relacionamento 1 a 1 para o funcionário. Trabalho com uma variedade de bancos de dados o dia todo, como DBA e como desenvolvedor, e estou feliz por ainda não encontrar um com a estrutura que você propôs. Especialmente do ponto de vista de um desenvolvedor, seria um pesadelo escrever e verificar todos os erros, porque você não saberia de qual tabela ele veio. Mesmo escrevendo uma junção, os dados retornados ao software teriam um campo com dados nulos que ainda exigiriam que você testasse isso.
Nicholas Aguirre
13

Além de todos os problemas com desenvolvedores confusos NULL, os NULLs têm outra desvantagem muito séria: Desempenho

As colunas NULL'able são um desastre da perspectiva de desempenho. Considere números inteiros aritméticos como exemplo. Em um mundo sadio sem NULL, é "fácil" vetorizar a aritmética inteira no código do mecanismo de banco de dados usando instruções SIMD para executar praticamente qualquer cálculo em velocidades mais rápidas que 1 linha por ciclo de CPU. No entanto, no momento em que você introduz o NULL, você precisa lidar com todos os casos especiais que o NULL cria. Os modernos conjuntos de instruções da CPU (leia também: lógica x86 / x64 / ARM e GPU) simplesmente não estão equipados para fazer isso com eficiência.

Considere a divisão como um exemplo. Em um nível muito alto, esta é a lógica que você precisa com um número inteiro não nulo:

if (b == 0)
  do something when dividing by error
else
  return a / b

Com NULL, isso se torna um pouco mais complicado. Juntamente com bvocê, será necessário um indicador se bfor nulo e similar para a. A verificação agora se torna:

if (b_null_bit == NULL)
   return NULL
else if (b == 0) 
   do something when dividing by error
else if (a_null_bit == NULL)
   return NULL
else 
   return a / b

A aritmética NULL é significativamente mais lenta para executar em uma CPU moderna do que a aritmética não nula (por um fator de cerca de 2-3x).

Fica pior quando você apresenta o SIMD. Com o SIMD, uma CPU Intel moderna pode executar divisões inteiras de 4 x 32 bits em uma única instrução, como esta:

x_vector = a_vector / b_vector
if (fetestexception(FE_DIVBYZERO))
   do something when dividing by zero
return x_vector;

Agora, existem maneiras de lidar com o NULL também no SIMD, mas isso requer o uso de mais vetores e registradores de CPU e o mascaramento inteligente de bits. Mesmo com bons truques, a penalidade de desempenho da aritmética de número inteiro NULL se aproxima do intervalo 5-10x mais lento, mesmo para expressões relativamente simples.

Algo como o descrito acima vale para agregados e, até certo ponto, para junções também.

Em outras palavras: A existência de NULL no SQL é uma incompatibilidade de impedância entre a teoria do banco de dados e o design real dos computadores modernos. Há uma boa razão para o NULL confundir os desenvolvedores - porque um número inteiro não pode ser NULL na maioria das linguagens de programação sãs - não é assim que os computadores funcionam.

Thomas Kejser
fonte
10

Perguntas interessantes.

Tudo o que consigo pensar é que, como desenvolvedor de aplicativos, você não precisaria testar NULL e um possível valor inexistente de dados (por exemplo, uma string vazia para strings).

É mais complicado que isso. Nulo tem vários significados distintos e uma razão realmente importante para não permitir nulos em muitas colunas é que, quando a coluna é nula, isso significa apenas uma e apenas uma coisa (a saber, que ela não apareceu em uma junção externa). Além disso, permite definir padrões mínimos de entrada de dados, o que é realmente útil.

Mas o que você faz no caso de datas, data e hora e hora (SQL Server 2008)? Você precisaria usar uma data histórica ou de fundo do poço.

Isso ilustra um problema com nulos imediatamente, ou seja, que um valor armazenado em uma tabela pode significar "esse valor não se aplica" ou "não sabemos". Com cadeias, uma cadeia vazia pode servir como "isso não se aplica", mas com datas e horas, não existe tal convenção porque não há valor válido que convencionalmente significa isso. Normalmente, você ficará preso usando NULLs.

Existem maneiras de contornar isso (adicionando mais relações e junção), mas elas apresentam exatamente os mesmos problemas de clareza semântica que os NULLs no banco de dados. Para esses bancos de dados, eu não me preocuparia com isso. Simplesmente não há nada que você possa fazer sobre isso.

EDIT: Uma área onde NULLs são indispensáveis ​​está em chaves estrangeiras. Aqui eles geralmente têm apenas um significado, idêntico ao nulo no significado da junção externa. Esta é uma exceção ao problema, é claro.

Chris Travers
fonte
10

O artigo da Wikipedia sobre SQL Null tem algumas observações interessantes sobre o valor NULL e, como resposta independente do banco de dados, desde que você esteja ciente dos possíveis efeitos de ter valores NULL para seu RDBMS específico, eles são aceitáveis ​​em seu design. Caso contrário, você não poderá especificar colunas como anuláveis.

Esteja ciente de como o RDBMS os trata em operações SELECT, como matemática, e também em índices.

Derek Downey
fonte
-12

Uau, a resposta correta "Não permita NULLs quando você não precisar, porque eles prejudicam o desempenho" é de alguma forma a última resposta classificada. Vou votá-lo e elaborar. Quando um RDBMS permite NULLs para uma coluna não esparsa, essa coluna é adicionada a um bitmap que rastreia se o valor é NULL para cada linha individual. Portanto, adicionando capacidade NULL a uma coluna em uma tabela em que todas as colunas não permitem NULLs, você aumenta o espaço de armazenamento necessário para salvar a tabela. Além disso, você está exigindo que o RDBMS leia e grave no bitmap, prejudicando o desempenho em todas as operações.

Além disso, em vários casos, permitir NULLs quebrará 3NF. Embora eu não seja um defensor da 3NF como muitos de meus colegas, considere o seguinte cenário:

Na tabela Pessoa, há uma coluna chamada DateOfDeath, que é anulável. Se uma pessoa morreu, ela será preenchida com o DateOfDeath, caso contrário, será deixado NULL. Há também uma coluna de bits não anulável chamada IsAlive. Esta coluna é definida como 1 se a pessoa estiver viva e 0 se a pessoa estiver morta. A grande maioria dos procedimentos armazenados usa a coluna IsAlive, eles se importam apenas se uma pessoa estiver viva, não seu DateOfDeath.

No entanto, a coluna IsAlive interrompe a normalização do banco de dados, porque é completamente derivável de DateOfDeath. Mas como o IsAlive está conectado à maioria dos SPs, a solução direta é tornar o DateOfDeath não nulo e atribuir um valor padrão à coluna no caso de a pessoa ainda estar viva. Os poucos SPs que usam DateOfDeath podem ser reescritos para verificar a coluna IsAlive e honrar o DateOfDeath apenas se a pessoa não estiver viva. Novamente, como a maioria dos SPs se preocupa apenas com o IsAlive (um pouco) e não com o DateOfDeath (uma data), esse padrão acelera consideravelmente o acesso.

Um script T-SQL útil para localizar colunas anuláveis ​​sem NULLs em todos os esquemas é:

select 'IF NOT EXISTS (SELECT 1 FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' WHERE ' + QUOTENAME(c.name) + ' IS NULL)
    AND (SELECT COUNT(*) FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ') > 1 PRINT ''' + s.name + '.' + t.name + '.' + REPLACE(c.name, '''', '''''') + ''''
    from sys.columns c
    inner join sys.tables t ON c.object_id = t.object_id
    inner join sys.schemas s ON s.schema_id = t.schema_id
    where c.is_nullable = 1 AND c.is_computed = 0
    order by s.name, t.name, c.name;

Se você executar isso em uma cópia do banco de dados de produção, poderá encontrar as colunas desenvolvedores marcadas como permitindo NULLs que não possuem NULLs na prática. A grande maioria deles pode ser marcada como NOT NULL, aumentando assim o desempenho e diminuindo o espaço de armazenamento.

Pode não ser possível eliminar todos os NULLs em todas as tabelas e ainda ter um design limpo, mas há uma vantagem considerável na eliminação do maior número possível de NULLs. O otimizador trabalha muito mais rápido com essas informações e, se você puder eliminar todos os NULLs em uma tabela, poderá recuperar uma quantidade considerável de espaço de armazenamento.

Eu sei que desempenho não é algo que os DBAs pensam muito, mas você só pode gastar uma quantidade limitada de memória e potência do processador em uma solução, em algum momento você precisará começar a pensar em design lógico e físico .

Observe também que isso é apenas para RDBMSes verdadeiros e estou baseando a parte técnica das minhas respostas no SQL Server. O T-SQL listado para encontrar colunas anuláveis ​​sem nulos também é do SQL Server.

Matthew Sontum
fonte
1
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
Paul White