SQL: COUNT(*) vs COUNT(coluna)

- Published on

No mundo da engenharia de dados e análise de SQL, um dos erros mais sutis — e ao mesmo tempo mais comuns — acontece ao usar LEFT JOIN. Você espera que um cliente sem pedidos retorne 0, mas o relatório teima em mostrar 1.
O problema pode não estar no LEFT JOIN, mas sim no que você está contando.
Vamos analisar o cenário clássico apresentado na imagem:

Considere as tabelas CUSTOMERS e ORDERS:
Tabela: CUSTOMERS
| customer_id |
|---|
| 101 |
| 102 |
| 103 |
Tabela: ORDERS
| order_id | customer_id |
|---|---|
| 501 | 101 |
| 502 | 101 |
| 503 | 102 |
O cenário:
- Cliente 101 → 2 pedidos
- Cliente 102 → 1 pedido
- Cliente 103 → Nenhum pedido
O requisito: Contar os pedidos de todos os clientes, incluindo aqueles que não fizeram nenhum pedido (zero).
O Problema: COUNT(*) vs COUNT(coluna)
1. COUNT(*) → Conta todas as linhas do JOIN
Quando usamos LEFT JOIN, a linha do cliente 103 é preservada mesmo sem um pedido correspondente. As colunas da tabela de pedidos ficam com valores NULL, mas a linha do cliente ainda existe no resultado.
O COUNT(*) conta essa linha preservada.
SELECT c.customer_id,
COUNT(*) AS joined_rows
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
GROUP BY c.customer_id
ORDER BY c.customer_id;
Resultado:
| customer_id | joined_rows |
|---|---|
| 101 | 2 |
| 102 | 1 |
| 103 | 1 |
O que aconteceu aqui? O COUNT(*) contou a linha do cliente 103, mesmo que os dados do pedido sejam NULL. Resultado: 1 (o que está incorreto para o nosso objetivo).

2. COUNT(o.order_id) → Conta apenas IDs de pedidos não nulos
Neste exemplo, order_id é uma chave primária não nula, com uma linha por pedido. Para o cliente 103, o order_id resultante do JOIN é NULL, então ele não contribui em nada para a contagem.
SELECT c.customer_id,
COUNT(o.order_id) AS order_count
FROM customers c
LEFT JOIN orders o
ON o.customer_id = c.customer_id
GROUP BY c.customer_id
ORDER BY c.customer_id;
Resultado:
| customer_id | order_count |
|---|---|
| 101 | 2 |
| 102 | 1 |
| 103 | 0 |
O que aconteceu aqui? O COUNT(coluna) ignora valores NULL. Como o order_id para o cliente 103 é NULL (não houve correspondência no JOIN), ele não é contado. Resultado: 0 (correto!).

Arquitetura e Detalhes Importantes
Mesmas tabelas. Mesmo JOIN. Significados diferentes. Antes de escolher a função de agregação, você precisa entender dois detalhes cruciais:
1. Substituir COUNT(*) por COUNT(1) não resolve o problema
A constante 1 não é nula, então a linha preservada ainda será contada. O cliente 103 continuaria retornando 1.
2. COUNT(o.order_id) não remove duplicatas
Cuidado com a ilusão de que contar uma coluna específica resolve todos os problemas. Se você adicionar mais JOINs (por exemplo, uma tabela de itens do pedido), um único pedido pode ser multiplicado em várias linhas, inflando a contagem novamente.
Conclusão
Conte a entidade, não apenas as linhas
Antes de escolher um agregador, faça a si mesmo duas perguntas fundamentais:
- O que cada linha do JOIN representa?
- Qual entidade eu estou tentando contar?

Regras práticas:
Use COUNT(*) quando você precisar do número de linhas resultantes do JOIN.
Use COUNT(identificador_não_nulo) quando você precisar contar entidades correspondentes (como pedidos reais), após verificar se o JOIN preserva uma linha por entidade.
Não conte apenas as linhas que o JOIN cria. Conte a entidade.
Fique ligado
Seja um Expert em Growth
Receba insights práticos sobre marketing, dados, performance e tecnologia direto no seu email.