NULLs em agregações, junções e DISTINCT
Veja como NULL se comporta de maneira diferente em agrupamentos, junções e exclusividade.
NULLs em agregações, junções e DISTINCT é uma aula grátis de SQL Interview Prep no CoddyKit. Esta é a aula 4 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de SQL Interview Prep, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de SQL Interview Prep inclui 4 aulas no total.
NULL em três lugares surpreendentes
NULL não se comporta da mesma forma em todos os contextos. A lição final aborda os três contextos cujo comportamento mais surpreende os candidatos: agregações, JOINs e DISTINCT / GROUP BY.
A diferença recorrente é que as agregações e a filtragem tratam NULL como “ignore-me”, enquanto o agrupamento e DISTINCT tratam NULL como “um valor igual a outros NULL”. Essa inconsistência é exatamente o que os entrevistadores exploram.
Domine esses conceitos e você terá completado o ciclo das perguntas mais comuns sobre NULL em entrevistas de SQL.
As agregações ignoram NULL
A regra principal é: as funções de agregação ignoram NULL. SUM, AVG, MIN, MAX e COUNT(column) ignoram completamente as entradas NULL, em vez de tratá-las como zero.
É por isso que AVG pode retornar um número diferente do esperado. Ele divide a soma dos valores não NULL pela quantidade de valores não NULL, e não pela quantidade total de linhas.
-- bonus values: 100, 200, NULL
SELECT
SUM(bonus) AS total, -- 300 (NULL ignored)
AVG(bonus) AS average, -- 150 = 300 / 2, not / 3
COUNT(bonus) AS cnt -- 2 (NULL not counted)
FROM employees;COUNT(*) versus COUNT(column)
Esta é a pergunta mais frequente sobre NULL em agregações. COUNT(*) conta as linhas, inclusive aquelas que contêm NULL. COUNT(column) conta apenas as linhas em que essa coluna é não NULL.
Portanto, a diferença entre as duas funções corresponde exatamente à quantidade de valores NULL nessa coluna. COUNT(DISTINCT column) vai além: também ignora NULL ao remover duplicatas.
SELECT
COUNT(*) AS rows_total, -- all rows
COUNT(bonus) AS non_null_bonus, -- excludes NULLs
COUNT(DISTINCT bonus) AS distinct_bonus, -- excludes NULLs + dups
COUNT(*) - COUNT(bonus) AS null_bonus
FROM employees;AVG versus SUM/COUNT(*): uma armadilha clássica
Os entrevistadores perguntam: “AVG(x) é igual a SUM(x) / COUNT(*)?”. A resposta é não quando há valores NULL.
AVG(x) é igual a SUM(x) / COUNT(x), dividindo pela quantidade de valores não NULL. Dividir por COUNT(*) em vez disso trata os valores NULL como se fossem zero, reduzindo a média.
Se você realmente quiser contar NULL como zero, deverá declarar isso explicitamente com COALESCE.
-- These differ when bonus has NULLs:
SELECT
AVG(bonus) AS avg_ignoring_nulls,
SUM(bonus) * 1.0 / COUNT(*) AS avg_nulls_as_zero,
AVG(COALESCE(bonus, 0)) AS explicit_nulls_as_zero
FROM employees;O caso extremo da agregação com apenas NULL
O que uma agregação retorna quando toda entrada é NULL ou quando não há linhas? Esta é uma distinção precisa que os entrevistadores apreciam:
SUM,AVG,MINeMAXsobre linhas totalmente NULL (ou sobre zero linhas) retornam NULL.COUNTsempre retorna 0, nunca NULL.
Assim, se um relatório exibir totais em branco, um SUM totalmente NULL é uma causa provável. Envolva-o em COALESCE para exibir 0.
-- No matching rows or all bonuses NULL:
SELECT SUM(bonus) FROM employees WHERE 1 = 0; -- NULL
SELECT COUNT(bonus) FROM employees WHERE 1 = 0; -- 0
-- Present a clean zero:
SELECT COALESCE(SUM(bonus), 0) FROM employees;NULL nas condições de JOIN
Na cláusula ON de um JOIN, NULL = NULL continua sendo UNKNOWN, portanto chaves NULL nunca correspondem em um JOIN por igualdade. Duas linhas que tenham uma chave de JOIN NULL não serão associadas.
Isso costuma surpreender quem faz JOIN usando chaves estrangeiras opcionais. Se o comportamento desejado for corresponder NULL a NULL, você precisará de um operador seguro para NULL (IS NOT DISTINCT FROM ou <=>) apresentado na lição anterior.
-- Rows with region IS NULL on both sides do NOT match
SELECT *
FROM a JOIN b ON a.region = b.region;
-- To match NULL-to-NULL (ANSI):
SELECT *
FROM a JOIN b ON a.region IS NOT DISTINCT FROM b.region;Valores NULL gerados por JOIN externo
Um JOIN externo gera valores NULL para linhas sem correspondência. Após um LEFT JOIN, toda coluna do lado direito será NULL nas linhas do lado esquerdo que não encontraram correspondência.
Essa é a base do padrão de anti-JOIN: filtre com WHERE right_table.key IS NULL para encontrar linhas sem correspondência, como clientes sem pedidos.
Tenha cuidado, porém: filtrar uma coluna do JOIN externo em WHERE pode convertê-lo acidentalmente novamente em um JOIN interno, tema da próxima cena.
-- Find customers who have never ordered (anti-join)
SELECT c.id, c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;A armadilha de NULL ao usar WHERE em JOIN externo
Uma armadilha muito comum. Você faz um LEFT JOIN com pedidos e depois adiciona WHERE o.status = 'shipped'. De repente, os clientes sem pedidos desaparecem, transformando seu JOIN externo em um JOIN interno na prática.
Por quê? Nas linhas sem correspondência, o.status é NULL, e NULL = 'enviado' é UNKNOWN; por isso, WHERE as elimina. Para preservar as linhas sem correspondência, mova a condição para a cláusula ON.
-- Accidental inner join: drops customers with no orders
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'shipped';
-- Correct: keep unmatched customers
SELECT c.name, o.status
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.id AND o.status = 'shipped';DISTINCT Considera Todos os NULL Iguais
Aqui está a inconsistência que surpreende todo mundo. As agregações ignoram NULL, mas DISTINCT mantém exatamente um NULL, tratando todos os NULL como duplicatas uns dos outros.
Assim, SELECT DISTINCT bonus sobre os valores 100, 100, NULL, NULL retorna três linhas: 100, NULL e nada mais. Os dois NULL são reduzidos a um só, embora NULL = NULL seja UNKNOWN em outros contextos.
-- bonus: 100, 100, NULL, NULL, 200
SELECT DISTINCT bonus FROM employees;
-- Returns: 100, 200, NULL (the two NULLs become one row)GROUP BY Reúne Todos os NULL em um Único Grupo
GROUP BY segue a mesma regra de DISTINCT: todas as chaves NULL são reunidas em um único grupo. Isso é o oposto da lógica de comparação, na qual os NULL nunca são iguais entre si.
Assim, agrupar por uma coluna que aceita NULL fornece uma linha representando todos os registros cuja chave é NULL, que geralmente é exatamente o que se deseja em relatórios. Mencione esse contraste (agrupamento versus comparação) para demonstrar domínio do assunto.
-- All employees with NULL department form ONE group
SELECT department, COUNT(*) AS headcount
FROM employees
GROUP BY department;
-- A single row where department is NULL totals all of themPontos para Abordar em Entrevistas
A síntese central que impressiona os entrevistadores:
- As agregações ignoram NULL; AVG divide por COUNT(coluna), não por COUNT(*).
- COUNT(*) conta linhas; COUNT(coluna) e COUNT(DISTINCT coluna) ignoram NULL.
- SUM/AVG/MIN/MAX sobre nenhuma linha retornam NULL; COUNT retorna 0.
- Em junções, chaves NULL nunca correspondem; filtrar uma coluna de uma junção externa em WHERE a transforma silenciosamente em uma junção interna.
- DISTINCT e GROUP BY tratam todos os NULL como iguais, o oposto da lógica de comparação.
A frase resumida: 'NULL é ignorado ao agregar e comparar, mas é agrupado ao remover duplicatas.'
Verificação Rápida
Teste o contraste entre agrupamento e agregação.
Recapitulação
Você concluiu o tratamento de NULL para entrevistas:
- As agregações ignoram NULL; AVG divide pela quantidade de valores não NULL, e uma soma com todos os valores NULL resulta em NULL, enquanto COUNT resulta em 0.
COUNT(*)inclui linhas NULL;COUNT(col)não inclui, e a diferença corresponde à quantidade de NULL.- Chaves de junção que são NULL nunca correspondem; filtrar colunas de uma junção externa em WHERE pode reduzi-la a uma junção interna.
- DISTINCT e GROUP BY reúnem todos os NULL em um só, o inverso da lógica de comparação.
Lembre-se da máxima: NULL é ignorado ao agregar e comparar, mas é agrupado ao remover duplicatas. Essa única ideia responde à maioria das perguntas de entrevista sobre NULL.
Perguntas Frequentes
A aula “NULLs em agregações, junções e DISTINCT” é grátis?
Sim — o texto completo de “NULLs em agregações, junções e DISTINCT” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de SQL Interview Prep, atualize para CoddyKit PRO. O curso de SQL Interview Prep inclui 4 aulas no total.
O que vou aprender em “NULLs em agregações, junções e DISTINCT”?
Veja como NULL se comporta de maneira diferente em agrupamentos, junções e exclusividade. Você pratica SQL Interview Prep com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar SQL Interview Prep?
Nenhuma experiência prévia é necessária. SQL Interview Prep no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 4 de 4.
Quanto tempo leva a aula “NULLs em agregações, junções e DISTINCT”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de SQL Interview Prep?
Sim. Cada aula de SQL Interview Prep inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Lógica de três valores e UNKNOWN
- IS NULL, IS NOT NULL e igualdade segura para NULL
- COALESCE, NULLIF e ISNULL
- NULLs em agregações, junções e DISTINCT