Um dos erros mais caros em SQL não é um SELECT com sintaxe quebrada. É uma query que roda sem reclamar, entrega um número bonito e está errada porque o JOIN multiplicou linhas no caminho.
Esse tipo de erro é traiçoeiro porque parece análise pronta. O dashboard abre, a tabela soma, a reunião anda. Só depois alguém percebe que a receita ficou maior do que a real, que o volume de pedidos dobrou ou que a taxa de conversão perdeu o sentido.
Na prática, o problema quase nunca está no comando JOIN em si. Está na granularidade que você esqueceu de validar antes de cruzar as tabelas.
O que realmente acontece quando um JOIN duplica linhas
Vamos supor um cenário comum:
- uma tabela de
pedidos; - uma tabela de
itens_pedido; - uma tabela de
clientes.
Se você quer calcular receita por cliente e junta clientes com pedidos e depois com itens_pedido, precisa saber o que cada linha representa em cada etapa.
clientes: uma linha por cliente;pedidos: uma linha por pedido;itens_pedido: várias linhas por pedido.
Se você soma o valor de pedidos.total_pedido depois de entrar em itens_pedido, esse total pode ser repetido uma vez para cada item do pedido. O banco não está errado. Ele só está devolvendo todas as combinações válidas segundo a sua regra de junção.
O erro é analítico: a pergunta exigia uma granularidade, mas a query foi montada em outra.
O sinal clássico: a métrica cresce sem explicação de negócio
O primeiro alerta costuma ser este: depois do JOIN, o número cresce demais e ninguém consegue explicar por quê.
Exemplos típicos:
- receita maior do que o faturamento conhecido;
- mais pedidos do que IDs únicos de pedido;
- mais leads convertidos do que registros no CRM;
- mais usuários do que a própria base de usuários.
Quando isso acontece, muita gente corre para DISTINCT, troca agregação ou mexe em filtro. Às vezes até “resolve” o número. Mas, sem entender a causa, você só troca um erro visível por um erro silencioso.
O erro de raciocínio por trás do problema
O padrão costuma ser um destes:
- A pessoa assume que a chave do JOIN é única dos dois lados, mas ela não é.
- A pessoa junta uma tabela detalhada antes de agregar.
- A pessoa soma uma métrica já consolidada depois de entrar em uma tabela mais granular.
- A pessoa não valida o que uma linha representa em cada tabela.
Em todas essas situações, a pergunta correta não é “qual JOIN usar?”. A pergunta correta é “qual deve ser a unidade final da análise?”.
Se a resposta é “uma linha por pedido”, todo o resto da query precisa respeitar isso.
Como diagnosticar sem perder tempo
Quando eu suspeito de duplicidade em JOIN, sigo uma sequência simples.
1. Contar linhas antes e depois do JOIN
Comece pelo básico:
SELECT COUNT(*) AS linhas_pedidos
FROM pedidos;
Depois:
SELECT COUNT(*) AS linhas_pos_join
FROM pedidos p
LEFT JOIN itens_pedido i
ON p.id_pedido = i.id_pedido;
Se o volume sobe de forma relevante, isso não prova erro sozinho, mas confirma que houve multiplicação de linhas.
2. Testar a cardinalidade da chave
Agora verifique se a chave do lado direito aparece mais de uma vez:
SELECT
id_pedido,
COUNT(*) AS qtd_linhas
FROM itens_pedido
GROUP BY id_pedido
HAVING COUNT(*) > 1;
Se esse resultado existir, você já sabe que um pedido pode virar várias linhas depois do JOIN.
3. Comparar contagem distinta com contagem total
SELECT
COUNT(*) AS linhas_total,
COUNT(DISTINCT p.id_pedido) AS pedidos_distintos
FROM pedidos p
LEFT JOIN itens_pedido i
ON p.id_pedido = i.id_pedido;
Quando linhas_total é maior do que pedidos_distintos, sua tabela final não está mais na granularidade de pedido.
4. Inspecionar um caso real
Escolha um ID específico e abra o detalhe:
SELECT
p.id_pedido,
p.total_pedido,
i.id_item,
i.valor_item
FROM pedidos p
LEFT JOIN itens_pedido i
ON p.id_pedido = i.id_pedido
WHERE p.id_pedido = 12345;
Esse passo é importante porque transforma um problema abstrato em algo visível. Você vê a repetição acontecendo.
O erro mais comum de correção: esconder com DISTINCT
DISTINCT tem utilidade, mas vira muleta fácil.
Se você faz isto:
SELECT DISTINCT
p.id_pedido,
p.total_pedido
FROM pedidos p
LEFT JOIN itens_pedido i
ON p.id_pedido = i.id_pedido;
talvez a lista de pedidos volte a parecer correta. Só que isso não garante que a lógica da análise esteja certa quando entram outras colunas, outras métricas ou novas tabelas no mesmo fluxo.
O ponto é simples: DISTINCT remove sintomas. Nem sempre corrige a modelagem da consulta.
O jeito mais seguro de corrigir
Na maioria dos casos, a correção passa por agregar antes de juntar.
Se você precisa de informação de itens_pedido, primeiro reduza essa tabela para a granularidade que a análise exige:
WITH itens_por_pedido AS (
SELECT
id_pedido,
COUNT(*) AS qtd_itens,
SUM(valor_item) AS valor_itens
FROM itens_pedido
GROUP BY id_pedido
)
SELECT
p.id_pedido,
p.id_cliente,
p.total_pedido,
i.qtd_itens,
i.valor_itens
FROM pedidos p
LEFT JOIN itens_por_pedido i
ON p.id_pedido = i.id_pedido;
Agora o JOIN volta a conectar “uma linha por pedido” com “uma linha por pedido”. A chance de distorção cai muito.
O que validar antes de confiar na query final
Antes de publicar resultado, levar para dashboard ou usar em decisão, valide quatro pontos:
- o que cada linha representa na tabela final;
- se a chave de junção é única ou repetida em cada lado;
- se alguma métrica consolidada está sendo somada depois de entrar em detalhe;
- se o total final fecha com uma fonte de controle mais simples.
Esse último ponto é subestimado. Se sua query calcula receita por cliente, confira o total agregado contra uma consulta curta de faturamento. Se não fecha, o erro pode estar no JOIN, não no negócio.
Por que esse erro aparece tanto em times bons
Porque ele nasce em cenário real.
No mundo do trabalho, você raramente analisa uma tabela perfeita e isolada. Você cruza base de pedidos com itens, campanhas com sessões, leads com CRM, contratos com eventos, usuários com pagamentos. Quase sempre há múltiplas granularidades convivendo.
Quem trabalha bem com SQL não é quem sabe só escrever LEFT JOIN. É quem olha para uma tabela e pergunta: “o que uma linha representa aqui?”.
Essa pergunta evita boa parte das métricas falsas que parecem plausíveis.
Resumo direto
Quando um JOIN duplica linhas, o banco não está “inventando” dado. Ele está obedecendo a relação entre tabelas que você mandou usar.
O erro aparece quando a análise ignora cardinalidade, granularidade e ordem de agregação. Por isso, antes de corrigir com DISTINCT, conte linhas, valide chaves e agregue na unidade certa.
É isso que separa uma query que roda de uma query que você realmente pode defender.
Se quiser reforçar essa base, estes conteúdos ajudam na sequência:
