Database
 sql >> Base de Dados >  >> RDS >> Database

50 Shades of NULL – Os diferentes significados de NULL em SQL




Tony Hoare, que é mais conhecido como o inventor da referência NULL, agora chama isso de um erro de bilhões de dólares do qual praticamente todas as linguagens estão “sofrendo”, incluindo o SQL.

Citando Tony (de seu artigo da Wikipedia):
Eu chamo isso de meu erro de um bilhão de dólares. Foi a invenção da referência nula em 1965. Naquela época, eu estava projetando o primeiro sistema de tipos abrangente para referências em uma linguagem orientada a objetos (ALGOL W). Meu objetivo era garantir que todo uso de referências fosse absolutamente seguro, com a verificação realizada automaticamente pelo compilador. Mas não resisti à tentação de colocar uma referência nula, simplesmente porque era muito fácil de implementar. Isso levou a inúmeros erros, vulnerabilidades e falhas no sistema, que provavelmente causaram um bilhão de dólares de dor e danos nos últimos quarenta anos.
O interessante aqui é que Tony ficou tentado a implementar essa referência porque era fácil de fazer. Mas por que ele precisava de tal referência?

Os diferentes significados de NULL


Em um mundo perfeito, não precisaríamos de NULL. Cada pessoa tem um primeiro nome e um sobrenome. Cada pessoa tem uma data de nascimento, um emprego, etc. Ou tem?

Infelizmente, eles não.

Nem todos os países usam o conceito de nome e sobrenome.

Nem todas as pessoas têm um emprego. Ou, às vezes, não conhecemos o trabalho deles. Ou não nos importamos.

É aqui que NULL é extremamente útil. NULL pode modelar todos esses estados que não queremos modelar. NULO pode ser:
  • O valor "indefinido" , ou seja, o valor que ainda não está definido (provavelmente por razões técnicas), mas pode ser definido posteriormente. Pense em uma pessoa que queremos adicionar ao banco de dados para usá-lo em outras tabelas. Em algum estágio posterior, adicionaremos o trabalho dessa pessoa.
  • O valor "desconhecido" , ou seja, o valor que não sabemos (e talvez nunca saibamos). Talvez não possamos mais perguntar a essa pessoa ou a seus parentes sobre sua data de nascimento – a informação será perdida para sempre. Mas ainda queremos modelar a pessoa, então usamos NULL no sentido de UNKNOWN (que é seu verdadeiro significado em SQL, como veremos mais adiante).
  • O valor "opcional" , ou seja, o valor que não precisa ser definido. Observe que o valor “opcional” também aparece no caso de um OUTER JOIN, quando a junção externa não produz nenhum valor em um lado do relacionamento. Ou também ao usar GROUPING SETS, onde diferentes combinações de colunas GROUP BY são combinadas (ou deixadas em branco).
  • O valor "excluído" ou "evitado" , ou seja, o valor que não queremos especificar. Talvez costumamos registrar o estado civil de uma pessoa como é feito em algumas jurisdições, mas não em outras, onde não é legal registrar nenhum dado pessoal desse tipo. Portanto, não queremos saber esse valor em alguns casos.
  • O valor "especial" em um determinado contexto , ou seja, o valor que não podemos modelar de outra forma na faixa de valores possíveis. Isso geralmente é feito ao trabalhar com intervalos de datas. Vamos supor que o trabalho de uma pessoa seja limitado por duas datas e, se a pessoa estiver trabalhando nesse cargo, usaremos NULL para dizer que o período é ilimitado no final do período.
  • O NULO "acidental" , ou seja, o valor NULL que é apenas NULL porque os desenvolvedores não prestaram atenção. Na ausência de uma restrição NOT NULL explícita, a maioria dos bancos de dados assume que as colunas são anuláveis. E uma vez que as colunas são anuláveis, os desenvolvedores podem simplesmente "acidentalmente" colocar valores NULL em suas linhas, onde eles nem pretendiam.

Como vimos acima, esses são apenas alguns dos 50 Shades of NULL .

O exemplo a seguir exibe vários significados diferentes de NULL em um exemplo SQL concreto:




CREATE TABLE company (
    id int NOT NULL,
    name text NOT NULL,
    CONSTRAINT company_pk PRIMARY KEY (id)
);
CREATE TABLE job (
    person_id int NOT NULL,
    start_date date NOT NULL,

    -- If end_date IS NULL, the “special value” of an unbounded
    -- interval is encoded
    end_date date NULL,
    description text NOT NULL,

    -- A job doesn’t have to be done at a company. It is “optional”.
    company_id int NULL,
    CONSTRAINT job_pk PRIMARY KEY (person_id,start_date),
    CONSTRAINT job_company FOREIGN KEY (company_id) 
        REFERENCES company (id) 
);
CREATE TABLE person (
    id int  NOT NULL,
    first_name text NOT NULL,

    -- Some people need to be created in the database before we
    -- know their last_names. It is “undefined”
    last_name text NULL,

    -- We may not know the date_of_birth. It is “unknown”
    date_of_birth date NULL,

    -- In some situations, we must not define any marital_status.
    -- It is “deleted”
    marital_status int NULL,
    CONSTRAINT person_pk PRIMARY KEY (id),
    CONSTRAINT job_person FOREIGN KEY (person_id)
        REFERENCES person (id)
); 

As pessoas sempre discutiram sobre a ausência de um valor


Quando NULL é um valor tão útil, por que as pessoas continuam criticando?

Todos esses casos de uso anteriores para NULL (e outros) são exibidos nesta interessante e recente palestra de C.J. Date sobre “The Problem of Missing Information” (assista ao vídeo no YouTube).

O SQL moderno pode fazer muitas coisas incríveis que poucos desenvolvedores de linguagens de uso geral como Java, C#, PHP desconhecem. Vou mostrar um exemplo mais abaixo.

De certa forma, C.J. Date concorda com Tony Hoare que (ab)usar NULL para todos esses diferentes tipos de “informações ausentes” é uma escolha muito ruim.

Por exemplo, em eletrônica, técnicas semelhantes são aplicadas para modelar coisas como 1, 0, “conflito”, “não atribuído”, “desconhecido”, “não importa”, “alta impedância”. Observe, porém, como na eletrônica, diferentes valores especiais são usados ​​para essas coisas, em vez de um único valor NULL especial . Isso é realmente melhor? Como os programadores JavaScript se sentem sobre a distinção entre diferentes valores “falsos”, como “null”, “undefined”, “0”, “NaN”, a string vazia ‘’? Isso é realmente melhor?

Falando em zero:quando deixarmos o espaço SQL por um momento e entrarmos em matemática, veremos que culturas antigas como os romanos ou os gregos tiveram os mesmos problemas com o número zero. Na verdade, eles nem tinham como representar o zero ao contrário de outras culturas, como pode ser visto no artigo da Wikipedia sobre o número zero. Citando o artigo:
Os registros mostram que os antigos gregos pareciam inseguros sobre o status do zero como número. Eles se perguntavam:“Como nada pode ser alguma coisa?”, levando a discussões filosóficas e, no período medieval, religiosas sobre a natureza e a existência do zero e do vácuo.
Como podemos ver, os “argumentos religiosos” se estendem claramente à informática e ao software, onde ainda não sabemos ao certo o que fazer com a ausência de um valor.

De volta à realidade:NULL em SQL


Enquanto as pessoas (incluindo acadêmicos) ainda não concordam sobre o fato de precisarmos de alguma codificação para “indefinido”, “desconhecido”, “opcional”, “excluído”, “especial”, voltemos à realidade e às partes ruins sobre SQL é NULO.

Uma coisa que é frequentemente esquecida ao lidar com o NULL do SQL é que ele implementa formalmente o caso UNKNOWN, que é um valor especial que faz parte da chamada lógica de três valores, e o faz de forma inconsistente, por exemplo. no caso de operações UNION ou INTERSECT.

Se voltarmos ao nosso modelo:





Se, por exemplo, queremos encontrar todas as pessoas que não estão registradas como casadas, intuitivamente, gostaríamos de escrever a seguinte declaração:
SELECT * FROM person WHERE marital_status != 'married'

Infelizmente, devido à lógica de três valores e NULL do SQL, a consulta acima não retornará os valores que não possuem nenhum marital_status explícito. Portanto, precisaremos escrever um predicado explícito adicional:

SELECT * FROM person 
WHERE marital_status != 'married'
OR marital_status IS NULL

Ou, forçamos o valor para algum valor NOT NULL antes de compará-lo

SELECT * FROM person
WHERE COALESCE(marital_status, 'null') != 'married'

A lógica de três valores é difícil. E não é o único problema com NULL no SQL. Aqui estão mais desvantagens de usar NULL:

  • Existe apenas um NULL, quando realmente queríamos codificar vários valores “ausentes” ou “especiais” diferentes. O intervalo de valores especiais úteis depende muito do domínio e dos tipos de dados usados. No entanto, o conhecimento do domínio é sempre necessário para interpretar corretamente o significado de uma coluna anulável, e as consultas devem ser projetadas com cuidado para evitar que os resultados errados sejam retornados, como vimos acima.
  • Novamente, a lógica de três valores é muito difícil de acertar. Embora o exemplo acima ainda seja bastante simples, o que você acha que a consulta a seguir produzirá?
    SELECT * FROM person 
    WHERE marital_status NOT IN ('married', NULL)
    

    Exatamente. Não vai render nada, como explicado neste artigo aqui. Em suma, a consulta acima é a mesma que a abaixo:

    SELECT * FROM person 
    WHERE marital_status != 'married'
    AND marital_status != NULL -- This is always NULL / UNKNOWN
    

  • O banco de dados Oracle trata NULL e a string vazia '' como a mesma coisa. Isso é muito complicado, pois você não notará imediatamente por que a consulta a seguir sempre retorna um resultado vazio:

    SELECT * FROM person 
    WHERE marital_status NOT IN ('married', '')
    


  • Oracle (novamente) não coloca valores NULL em índices. Esta é a fonte de muitos problemas de desempenho desagradáveis, por exemplo, quando você está usando uma coluna anulável em um predicado NOT IN como tal:

    SELECT * FROM person 
    WHERE marital_status NOT IN (
      SELECT some_nullable_column
      FROM some_table
    )
    

    Com o Oracle, o anti-join acima resultará em uma verificação completa da tabela, independentemente de você ter um índice em some_nullable_column. Por causa da lógica de três valores e porque o Oracle não coloca NULLs em índices, o mecanismo precisará acessar a tabela e verificar todos os valores apenas para ter certeza de que não há pelo menos um valor NULL no conjunto, o que tornaria o predicado inteiro DESCONHECIDO.

Conclusão


Ainda não resolvemos o problema NULL na maioria das linguagens e plataformas. Embora eu afirme que NULL NÃO é o erro de um bilhão de dólares pelo qual Tony Hoare tenta se desculpar, NULL certamente também está longe de ser perfeito.

Se você quiser ficar do lado seguro com seu design de banco de dados, evite NULLs a todo custo, a menos que você precise absolutamente de um desses valores especiais para codificar usando NULL. Lembre-se, esses valores são:“indefinido”, “desconhecido”, “opcional”, “excluído” e “especial” e mais:Os 50 tons de NULL . Se você não estiver em tal situação, sempre adicione uma restrição NOT NULL para cada coluna em seu banco de dados. Seu design ficará muito mais limpo e seu desempenho muito melhor.

Se apenas NOT NULL fosse o padrão em DDL e NULLABLE a palavra-chave que precisava ser definida explicitamente…

Quais são suas tomadas e experiências com NULL? Como um SQL melhor funcionaria na sua opinião?

Lukas Eder é fundador e CEO da Data Geekery GmbH, localizada em Zurique, Suíça. A Data Geekery vende produtos e serviços de banco de dados em Java e SQL desde 2013.

Desde seus estudos de mestrado na EPFL em 2006, ele é fascinado pela interação de Java e SQL. A maior parte dessa experiência ele obteve na área de E-Banking suíço através de várias variantes (JDBC, Hibernate, principalmente com Oracle). Ele tem o prazer de compartilhar esse conhecimento em várias conferências, JUGs, apresentações internas e no blog da empresa.