Categorias
Geral

NULL

(sim, o título do artigo está certo e não é nenhum bug aqui do blog)

foto do buraco negro Messier 87 que parece com um círculo de fogo desfocado sobre fundo preto

Quase todo desenvolvedor profissional vai ter que lidar com um banco de dados relacional ao longo de sua carreira. Os bancos de dados relacionais são uma daquelas ideias boas que a computação trouxe para o mundo.

Obviamente esses sistemas possuem limitações e essas limitações se dão tanto no nível conceitual como nas várias implementações de SGBD exitentes.

A limitação do modelo relacional existe porque não dá pra modelar o mundo que nos cerca usando conceitos tão “bidimensionais” como tabelas.

Ok, eu sei que dá pra modelar 3D, séries temporais, hierarquia, graphos, etc em bancos de dados relacionais, mas você vão concordar que as coisas começam a ter que ser ligeiramente “enjambradas” nas tabelas pra isso funcionar. E o funcionar ainda pode trazer algumas limitações.

Também tem as limitações de implementações do modelo. Os SGBDs ainda precisam saber se aquela coluna vai armazenar um texto ou um número e qual o tamanho esse dado vai ter.

Mas tem um negócio que é praticamente onipresente nos bancos de dados relacionais. O NULL.

O NULL serve para dizer que aquele dado, daquela coluna, naquela linha “não existe”. Não importa se essa célula de informação é um texto, uma data ou uma chave primária/estrangeira.

Ele também é um tipo de dado “complementar”, ou seja, você não diz que uma coluna da tabela é do tipo NULL. Você diz que aquela coluna é do tipo X e também pode guardar um NULL. Ou seja, o valor NULL não é um tipo mas também serve a todos os tipos.

O NULL também é usado para modelar as relações entre os objetos de duas tabelas. É ele que vem como resposta ou influencia o resultado dos famigerados LEFT|RIGHT|INNER|OUTER|... JOINs que tanto demoram pra entrar na cabeça dos desenvolvedores.

O NULL é tão esquisito que força os programadores, tão acostumados com a lógica binária, a pensar em uma lógica com três estados.

Esses problemas mencionados até aqui podem ser extendidos aos nil, None de várias linguagens de programação, portanto, a dica que eu vou dar pode se aplicar em outros contextos: se você puder evitar usar NULL, faça isso.

Um exemplo do tipo de discussão sobre NULL causa aconteceu na nossa equipe. A gente está trabalhando em um sistema que cadastra perfis de cavalos. Então teremos nesse cadastro o nome do cavalo, a cor dele, a árvore genealógica, gênero, etc. Algumas dessas informações podem mudar ao longo do tempo (ex. um cavalo pode nascer de uma cor e mudar de cor na vida adulta). Outro problema que temos que lidar é com a fragmentação e qualidade dos dados das nossas fontes (ex. algumas base de dados que temos não informam a cor do animal).

Considerando esses requisitos e limitações é bastante comum que programadores, por reflexo, saiam colocando várias dessas colunas como NULLABLE no banco de dados. Mas isso trás alguns problemas que eu pretendo demonstrar (provavelmente de forma incompleta) abaixo.

Perda de Otimizações

Alguns SGBD podem perder otimizações em cenários onde temos colunas NULLABLE. Esse artigo aqui (inglês) tem uma explicação mais detalhada de um desses problemas.

O tipo de problema de otimização causado por colunas NULLABLE variam de SGBD pra SGBD, então recomendo que você faça uma busca por “nullable optimization [seu banco de dados]” no seu buscador favorito para entender o impacto do NULL no seu SGBD.

Pobreza Semântica

Quando usamos NULL no lugar de um valor real sabemos apenas que não temos aquele valor. Mas o que isso significa de fato? Não dá pra saber.

Vou dar um exemplo bem simplificado para ilustrar melhor… Imagine que temos um site de e-commerce e na nossa tabela de produtos (ex. Product) a gente tenha uma coluna para guardar o diâmetro do produto (ex. diameter). Na sua loja virtual você tem produtos com essa característica (ex. parafusos, canos, etc) e produtos que não tem essa característica (ex. caixa decorativa, piso porcelanato, furadeira).

A gente pensaria: esse campo é NULLABLE porque ele não precisa ser preenchido para todos os produtos.

Mas o que o não-valor NULL significa de verdade nesse contexto? significa que eu “não sei o valor” porque ainda não medi o objeto que está cadastrado no meu banco de dados? Significa que o valor diâmetro não se aplica àquele produto porque ele é uma caixa? Significa que ele ainda está aguardando a informação porque ela é preenchida de forma assíncrona por outro serviço ou equipe?

Não existe uma solução específica para adicionar mais semântica para esses dados. Existe um conjunto de técnicas e práticas que podem ser usadas pra resolver esse problema.

No caso da cor do cavalo que comentei acima, temos uma Foreign Key (FK) para uma tabela de cores oficiais de cavalo (sim, isso existe), o ideal seria criar uma cor NOT_AVAILABLE na tabela de cores e referenciar ela quando não conseguirmos determinar a cor do animal. Mas a mesma solução não serviria para a data de nascimento dele.

Como temos diferentes tipos de informação que podem ou não estar disponíveis precisamos criar uma modelagem específica para lidar com isso.

Coalescing

Quando dizemos que uma coluna é de um tipo específico podemos fazer nossas queries e nosso código sempre assumindo que o dado retornado é daquele tipo.

Saber disso diminui a complexidade do nosso código porque não precisamos ficar lidando com um cenário excepcional onde o tipo do dado muda nem ficar convertendo esse dado de um formato para outro.

NULL é inevitável

Infelizmente nem sempre é possível evitar o uso de NULL. Eventualmente precisamos recorrer à ele ou preferir ele à outras opções.

Se eu tenho uma coluna birthdate e nem sempre eu terei essa informação para preencher no meu banco de dados é preferível usar NULL do que armazenar uma data inválida tipo 00/00/0000. Usar uma data inválida só vai servir pra mudar a complexidade de lugar (quando precisar calcular a idade da pessoa preciso excluir datas zeradas pra não ter alguém com 2020 anos).

No caso do diâmetro que eu mencionei acima, podemos usar um 0 pra sinalizar que o objeto não tem diâmetro. Mas fazer isso pode trazer problemas e complexidades para o sistema de shipping fazer o cálculo da volumetria do objeto pra calcular o frete. Nesse caso usar NULL pode fazer mais sentido (e usar uma segunda coluna pra adicionar semântica à esse NULL).

Conclusão

NULL não é inerentemente ruim e não estou desaconselhando ele. O objetivo desse artigo é só “desligar o automático” na cabeça dos desenvolvedores na hora de tornar uma coluna NULLABLE.

E quando estiver usando NULL é importante redobrar a atenção com seu código e com seus dados.

PS. NULL significa Zero em Alemão. Então eu abro exceção e tomo Coca-Cola Null Zucker (zero açucar) por aqui. 😉