SELECT: extraindo os dados certos

NULL: quando o valor não existe

23 min
O que você vai aprender
  • entender que NULL não é zero, nem string vazia, nem um «valor ruim», e sim a ausência de valor
  • verificar valores ausentes com IS NULL e IS NOT NULL
  • explicar por que x = NULL, x <> NULL e até NULL = NULL não funcionam como parece
  • entender a lógica de três valores do SQL: TRUE, FALSE e UNKNOWN
  • prever quais linhas passam pelo WHERE e quais são descartadas
  • colocar valores de reserva na saída com COALESCE, sem alterar os dados da tabela
  • evitar os erros mais comuns em entrevistas e em consultas de verdade

NULL é quando não existe valor

Nos hologramas dos arquivos antigos aparecem falhas: uma célula não acende e, através dela, vê-se a escuridão.

No começo parece um erro. Será que esqueceram de exibir o valor? Será que ali deveria haver um zero? Ou uma string vazia?

Mas o Arquivo é honesto. Onde ele não sabe a resposta, não inventa uma.

NULL em SQL significa: não existe valor.

Não é «o valor é igual a zero».
Não é «ali tem um texto vazio».
Não é «ali houve um erro».
É exatamente isto: o valor está ausente ou é desconhecido.

Veja, por exemplo, a tabela users:

SELECT id, name, city
FROM users;

Resultado:

idnamecity
1AnnaMoscou
2Boris
3VeraKazan
4Gleb

À primeira vista, as linhas 2 e 4 se parecem: Boris e Gleb aparentemente não têm cidade. Mas, para o SQL, são situações diferentes.

Boris tem NULL em city — a cidade é desconhecida ou não foi informada.

Gleb pode ter uma string vazia '' em city — e isso já é um valor, apenas um texto com 0 caractere.

Essa diferença é importante.

NULL  -- não existe valor
''    -- existe valor, mas é um texto vazio
0     -- existe valor, e é o número zero

NULL não é um valor. É uma marca especial: o valor está ausente.

QUERY: Respeite os buracos na memória. Um banco que diz honestamente «não sei» é mais confiável do que um que mente com convicção.

NULL, zero e string vazia são coisas diferentes

Quem está começando costuma enxergar o NULL como “vazio” e depois se espanta quando as consultas se comportam de um jeito estranho.

Vamos ver isso num exemplo simples.

Digamos que a tabela products tenha uma coluna discount_percent:

idtitlediscount_percent
1Ração “Atum Lunar”10
2Tigela antigravitacional0
3Caminha do arquivista

O que significa cada linha?

O primeiro item tem 10% de desconto.

O segundo item tem 0% de desconto. Ou seja: o desconto é conhecido e vale zero.

No terceiro item está um NULL. Ou seja: não sabemos o desconto — ele não foi informado, não foi calculado ou ainda não foi carregado.

Ou seja:

discount_percent = 0

e

discount_percent IS NULL

— são estados diferentes.

O mesmo vale para texto.

city = ''

significa: a cidade está registrada como uma string vazia.

city IS NULL

significa: não há cidade nenhuma naquela célula.

Em bancos reais isso importa. Uma string vazia pode vir de um formulário em que a pessoa apagou o texto e enviou o campo em branco. Já um NULL pode significar que o campo nunca chegou a ser preenchido, que os dados ainda não vieram de outro serviço ou que o valor é mesmo desconhecido.

idfull_namecity1AnnaMoscow2IvanNULL3MariaKazanIvan has no city — the cell is NULL
A segunda linha não tem cidade — ali está um NULL. Não é string vazia nem zero, mas ausência de valor.

Por que = NULL não funciona

Agora, a maior pegadinha.

Parece que, para encontrar os usuários sem cidade, bastaria escrever assim:

SELECT id, name, city
FROM users
WHERE city = NULL;

Mas uma consulta dessas não encontra as linhas com NULL.

O motivo é que NULL não é um valor comum. Ele não pode ser comparado com = do mesmo jeito que um número ou um texto.

O SQL raciocina mais ou menos assim:

city = 'Москва'

Isso dá para verificar. Se city guarda 'Москва', a resposta é TRUE. Se guarda 'Казань', a resposta é FALSE.

Já isto:

city = NULL

O SQL não consegue responder TRUE nem FALSE, porque NULL significa “o valor é desconhecido”.

Se a cidade é desconhecida, dá para dizer que ela é igual a NULL? Não.
Dá para dizer que ela é diferente de NULL? Também não.

A resposta do SQL é: .

Ou seja: “desconhecido”.

No SQL não existem só dois resultados lógicos:

TRUE
FALSE

Existe um terceiro:

UNKNOWN

Isso se chama lógica de três valores.

TRUEdefinitely yesFALSEdefinitely noUNKNOWN?unknownx = NULLUNKNOWNWHERE keeps a row only when TRUE
As três respostas do arquivo: TRUE, FALSE e UNKNOWN — qualquer comparação com NULL usando = ou <> produz a terceira, e o WHERE não deixa essa linha passar.
Comparações com NULL se comportam de um jeito especial. Os operadores comuns = e <> não dão nem TRUE nem FALSE: o resultado que aparece na saída é NULL, ou seja, o valor lógico UNKNOWN.

O WHERE só deixa passar TRUE

Agora um passo importante.

O WHERE mantém a linha só quando a condição devolveu TRUE.

Se a condição devolveu FALSE, a linha é descartada.

Se a condição devolveu UNKNOWN, a linha também é descartada.

Ou seja, para o WHERE vale esta regra:

Resultado da condiçãoA linha entra no resultado?
TRUEsim
FALSEnão
não

É exatamente por isso que a consulta:

SELECT id, name, city
FROM users
WHERE city = NULL;

não devolve os usuários sem cidade.

Para uma linha em que city é NULL, a expressão:

city = NULL

não dá TRUE, e sim UNKNOWN.

E o WHERE só deixa passar TRUE.

Como testar NULL do jeito certo

Para o NULL existem verificações próprias:

coluna IS NULL

e

coluna IS NOT NULL

Se você precisa encontrar os usuários que não têm cidade informada:

SELECT id, name, city
FROM users
WHERE city IS NULL;

Se precisa encontrar os usuários que têm cidade informada:

SELECT id, name, city
FROM users
WHERE city IS NOT NULL;

Guarde a regra principal:

-- errado
WHERE city = NULL

-- errado
WHERE city <> NULL

-- certo
WHERE city IS NULL

-- certo
WHERE city IS NOT NULL

O IS NULL não compara o valor com NULL. Ele faz outra pergunta:

Está faltando valor nesta célula?

E o IS NOT NULL pergunta:

Esta célula tem valor?

IS NULL e IS NOT NULL são o jeito certo de filtrar linhas pela ausência ou pela presença de valor.

Por que o <> também pode surpreender

Outro erro frequente aparece com o operador <>.

Digamos que você precise encontrar todos os usuários que não são de Moscou.

Quem está começando escreve:

SELECT id, name, city
FROM users
WHERE city <> 'Москва';

Parece que a consulta deveria devolver:

  • os usuários de Kazan
  • os usuários de outras cidades
  • os usuários que não têm cidade informada

Mas as linhas com NULL não entram no resultado.

Por quê?

Para a linha com a cidade 'Казань':

city <> 'Москва'

TRUE.

Para a linha com a cidade 'Москва':

city <> 'Москва'

FALSE.

Para a linha com NULL:

city <> 'Москва'

UNKNOWN.

E o WHERE só deixa passar TRUE.

Por isso a linha com a cidade desconhecida é descartada.

Para incluir os usuários que não têm cidade informada, é preciso acrescentar a condição de forma explícita:

SELECT id, name, city
FROM users
WHERE city <> 'Москва'
   OR city IS NULL;

Agora a lógica é esta:

Mostre todos os que não são de Moscou e também aqueles cuja cidade não foi informada.

No PostgreSQL existe ainda um operador prático:

WHERE city IS DISTINCT FROM 'Москва'

Ele lida com o NULL de forma mais previsível e devolve estritamente TRUE ou FALSE.

Por exemplo:

NULL IS DISTINCT FROM 'Москва'

devolve TRUE.

E:

NULL IS NOT DISTINCT FROM NULL

devolve TRUE.

Mas, para quem está começando, a regra básica continua a mesma: quando você precisa lidar com um valor ausente, use IS NULL e IS NOT NULL.

O <> não inclui as linhas com NULL, porque comparar com um valor desconhecido dá UNKNOWN.

Cuidado com NOT, AND e OR

É nas condições compostas que o NULL mais quebra expectativas.

Olhe esta condição:

WHERE NOT (city = 'Москва')

À primeira vista, ela parece igual a:

WHERE city <> 'Москва'

E, de fato, para valores comuns o resultado é parecido.

Mas, se city for NULL, a expressão:

city = 'Москва'

UNKNOWN.

E agora o ponto importante:

NOT UNKNOWN

também dá UNKNOWN.

Não TRUE.

Por isso a linha com NULL continua fora do resultado.

Exemplo:

SELECT id, name, city
FROM users
WHERE NOT (city = 'Москва');

As linhas com cidade desconhecida não passam.

Para incluí-las, é preciso escrever de forma explícita:

SELECT id, name, city
FROM users
WHERE city <> 'Москва'
   OR city IS NULL;

Vale guardar esta regra:

O NOT não transforma UNKNOWN em TRUE.

No SQL, UNKNOWN continua sendo desconhecido mesmo depois da negação.

COALESCE: uma luz de reserva para os buracos

Às vezes um NULL nos dados é uma situação perfeitamente normal, mas mostrá-lo assim num relatório não ajuda quem lê.

Por exemplo:

SELECT id, name, city
FROM users;

Resultado:

idnamecity
1AnnaMoscou
2Boris

Dentro do banco, o NULL é claro. Mas, numa interface ou num relatório, é melhor mostrar à pessoa um rótulo legível:

cidade não informada

Para isso usamos o COALESCE.

O COALESCE devolve o primeiro argumento que não for NULL.

SELECT COALESCE(NULL, NULL, 'valor de reserva');

Resultado:

coalesce
valor de reserva

Vamos aplicá-lo à tabela:

SELECT
    id,
    name,
    COALESCE(city, 'cidade não informada') AS city_for_report
FROM users;

Resultado:

idnamecity_for_report
1AnnaMoscou
2Boriscidade não informada
3VeraKazan

Importante: o COALESCE não muda os dados da tabela.

Ele muda apenas a saída da consulta.

Ou seja, na tabela, o Boris continua com um NULL. É só no resultado da consulta que mostramos um texto compreensível no lugar dele.

É como a plaquinha numa vitrine vazia: não apareceu produto nenhum, apareceu uma explicação para quem olha.

O COALESCE ajuda a trocar o NULL por um valor de reserva no resultado da consulta, sem alterar os dados originais.

O COALESCE pode escolher entre várias opções

O COALESCE não aceita só dois argumentos, e sim quantos você quiser.

Ele vai da esquerda para a direita e devolve o primeiro argumento que não for NULL.

Por exemplo, na tabela de usuários pode haver várias formas de contato:

idnametelegramemailphone
1Anna@annaanna@mail.test
2BorisNULLboris@mail.testNULL
3VeraNULLNULL+7001

Queremos mostrar o melhor contato disponível: primeiro o Telegram, se houver; se não, o email; se não, o telefone; e, se não houver nada, o texto “sem contato”.

SELECT
    id,
    name,
    COALESCE(telegram, email, phone, 'sem contato') AS best_contact
FROM users;

Resultado:

idnamebest_contact
1Anna@anna
2Borisboris@mail.test
3Vera+7001

A lógica é esta:

pegue telegram
se telegram for NULL — pegue email
se email for NULL — pegue phone
se phone for NULL — pegue 'sem contato'

Esse é um padrão muito comum em relatórios, exportações e interfaces.

Um detalhe técnico importante: os argumentos do COALESCE precisam ter tipos compatíveis.

Assim funciona:

COALESCE(city, 'cidade não informada')

porque as duas opções são texto.

Já assim pode dar erro:

COALESCE(discount_percent, 'sem desconto')

Se discount_percent for um número e 'sem desconto' for texto, o banco pode não entender que tipo de resultado você espera.

Nesses casos, o normal é converter o número para texto explicitamente antes:

COALESCE(discount_percent::text, 'sem desconto')

NULL no SELECT e NULL no WHERE são histórias diferentes

É importante distinguir duas situações.

No SELECT, você pode ver o NULL no resultado:

SELECT id, name, city
FROM users;

Aqui o NULL só aparece como o valor de uma célula do resultado. Cada editor de SQL pode mostrá-lo de um jeito: como NULL, como uma célula vazia ou como uma marca especial.

Já no WHERE o NULL influencia a filtragem:

SELECT id, name, city
FROM users
WHERE city <> 'Москва';

Aqui a linha com NULL pode sumir do resultado, porque a condição virou UNKNOWN.

Ou seja: o SELECT mostra os dados, e o WHERE decide se a linha passa ou não.

Para o WHERE, o que importa não é como a célula fica bonita na tela, e sim se a condição devolveu estritamente TRUE.

Erros comuns com NULL

Erro 1. Procurar NULL com =

WHERE city = NULL

Não escreva assim. Essa condição não encontra as linhas com NULL.

Certo:

WHERE city IS NULL

Erro 2. Procurar valores preenchidos com <> NULL

WHERE city <> NULL

Também não escreva assim. Essa condição não encontra as linhas preenchidas.

Certo:

WHERE city IS NOT NULL

Erro 3. Achar que o <> inclui NULL

WHERE city <> 'Москва'

Essa consulta encontra as cidades que com certeza não são Moscou. Mas ela não devolve as linhas em que a cidade é desconhecida.

Se você precisa tanto de “não é Moscou” quanto de “cidade não informada”:

WHERE city <> 'Москва'
   OR city IS NULL

Erro 4. Confundir string vazia com NULL

WHERE city = ''

Isso procura a string vazia, não o NULL.

Para encontrar as duas situações:

WHERE city = ''
   OR city IS NULL

Com dados sujos, às vezes é preciso verificar as duas coisas.

Erro 5. Usar COALESCE e achar que os dados mudaram

SELECT COALESCE(city, 'cidade não informada') AS city
FROM users;

Essa consulta muda apenas a exibição do resultado.

Ela não grava 'cidade não informada' na tabela.

Para mudar os dados de verdade é preciso um UPDATE, mas isso já é outra operação e outra responsabilidade.

A regra principal desta aula

NULL não pode ser testado com comparações comuns.

Escreva assim:

WHERE column IS NULL

ou assim:

WHERE column IS NOT NULL

Não escreva assim:

WHERE column = NULL

nem assim:

WHERE column <> NULL

O WHERE só deixa passar as linhas em que a condição devolveu TRUE.

FALSE e UNKNOWN não chegam ao resultado.

Interview question

Pergunta de entrevista:
Por que WHERE city = NULL não encontra as linhas em que a cidade não está preenchida?

Resposta forte:
NULL não é um valor comum, e sim a ausência de valor. Por isso a comparação city = NULL não retorna TRUE, mesmo quando city realmente contém NULL. O resultado é UNKNOWN. E o WHERE só deixa passar as linhas em que a condição deu TRUE. Logo, para testar um valor ausente é preciso escrever city IS NULL.


Pergunta de entrevista:
Por que WHERE city <> 'Москва' não retorna as linhas em que a cidade não está preenchida?

Resposta forte:
Se city for NULL, a expressão city <> 'Москва'UNKNOWN, e não TRUE. O WHERE só deixa passar TRUE, então as linhas com NULL ficam de fora. Para incluí-las no resultado, escreva:

WHERE city <> 'Москва'
   OR city IS NULL

No PostgreSQL, também dá para usar:

WHERE city IS DISTINCT FROM 'Москва'

Esse operador trata NULL como um estado comparável à parte e retorna estritamente TRUE ou FALSE.


Pergunta de entrevista:
Em que NULL difere do zero e da string vazia?

Resposta forte:
Zero e string vazia são valores. O zero é o valor numérico 0; a string vazia é um texto com 0 caractere. Eles são iguais a si mesmos e participam das comparações comuns. NULL significa que o valor não existe ou é desconhecido. Por isso comparações com NULL usando =, <>, <, > dão UNKNOWN. Para testar NULL usamos IS NULL e IS NOT NULL.


Pergunta de entrevista:
O que NULL = NULL retorna?

Resposta forte:
Não retorna TRUE, e sim UNKNOWN. Em SQL, NULL significa valor desconhecido. Dois valores desconhecidos não podem ser considerados iguais só porque ambos são desconhecidos. Por isso, para testar a ausência de valor não se usa = NULL, e sim IS NULL.


Pergunta de entrevista:
O que o COALESCE faz?

Resposta forte:
O COALESCE retorna o primeiro argumento que não for NULL. Ele é muito usado no SELECT para colocar um valor de reserva num relatório ou numa interface. Por exemplo:

COALESCE(city, 'cidade não informada')

Se city estiver preenchida, volta a cidade. Se city for NULL, volta o texto 'cidade não informada'. E os dados da tabela continuam intactos.

Check yourself
Como verificar corretamente que uma coluna não tem valor?
Check yourself
O que a expressão NULL = NULL devolve?
Check yourself
Quais linhas o WHERE deixa passar?
Check yourself
O que o COALESCE(city, 'cidade não informada') faz?

QUERY: O vazio também fala. O importante é não obrigá-lo a se passar por um valor.