Por que junções são essenciais em análises do dia a dia
Em bases de dados usadas no trabalho (vendas, suporte, financeiro, logística), é comum que as informações estejam distribuídas em várias tabelas. Um pedido pode estar em uma tabela, o cliente em outra, os itens do pedido em outra e o pagamento em outra. Para responder perguntas de negócio, você precisa “juntar” essas peças de informação de forma consistente. É exatamente isso que as junções (JOINs) fazem: combinam linhas de duas (ou mais) tabelas a partir de uma condição de relacionamento.
Na prática, INNER JOIN e LEFT JOIN cobrem a maior parte das necessidades do dia a dia. O INNER JOIN retorna apenas os registros que têm correspondência nas duas tabelas. O LEFT JOIN retorna todos os registros da tabela da esquerda, trazendo dados da direita quando houver correspondência — e preenchendo com NULL quando não houver.
Dados fictícios (realistas) para os exemplos
Para manter os exemplos próximos do mundo real, vamos usar um cenário de e-commerce com tabelas típicas: clientes, pedidos e pagamentos. Os nomes e valores são fictícios, mas a estrutura é a que você encontra em sistemas reais.
Tabelas e colunas
customers (cadastro de clientes)
- customer_id (PK)
- full_name
- state
- created_at
orders (pedidos)
Continue em nosso aplicativo
Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.
ou continue lendo abaixo...Baixar o aplicativo
- order_id (PK)
- customer_id (FK para customers)
- order_date
- status
- total_amount
payments (pagamentos)
- payment_id (PK)
- order_id (FK para orders)
- payment_date
- method
- amount
- status
Repare nas chaves: orders.customer_id aponta para customers.customer_id, e payments.order_id aponta para orders.order_id. Essas relações são o “mapa” das junções.
Conceito: como o JOIN combina linhas
Um JOIN pega cada linha da tabela A e procura linhas correspondentes na tabela B com base em uma condição (normalmente igualdade entre chaves). O resultado é uma tabela “virtual” com colunas de A e B lado a lado.
INNER JOIN: somente o que casa
Use INNER JOIN quando você quer apenas registros que existam nas duas tabelas. Exemplo: “Pedidos com cliente válido”. Se existir um pedido com customer_id que não aparece em customers (dados inconsistentes), ele não aparece no resultado.
LEFT JOIN: tudo da esquerda, com complemento da direita
Use LEFT JOIN quando você quer preservar todas as linhas da tabela principal (a da esquerda) e apenas enriquecer com dados da tabela da direita quando houver correspondência. Exemplo: “Todos os pedidos e, se houver, o pagamento”. Pedidos ainda não pagos continuam aparecendo, com colunas de pagamento como NULL.
Passo a passo: INNER JOIN na prática (clientes + pedidos)
Passo 1: defina a pergunta e a tabela principal
Pergunta: “Quais pedidos foram feitos e quem são os clientes?” A tabela principal aqui costuma ser orders, porque cada linha representa um pedido, e queremos anexar dados do cliente.
Passo 2: identifique a chave de ligação
A ligação é orders.customer_id = customers.customer_id.
Passo 3: escreva o INNER JOIN com aliases
Aliases deixam a consulta mais legível e evitam ambiguidade quando colunas têm o mesmo nome.
SELECT o.order_id, o.order_date, o.status, o.total_amount, c.customer_id, c.full_name, c.state FROM orders o INNER JOIN customers c ON o.customer_id = c.customer_id;O que você ganha com isso: uma visão “achatada” (denormalizada) com dados do pedido e do cliente na mesma linha, pronta para relatórios.
Passo 4: evite armadilhas de colunas com nomes iguais
Se ambas as tabelas tiverem uma coluna chamada status, por exemplo, você precisa prefixar com o alias (o.status ou c.status) para não gerar erro ou confusão. Mesmo quando não dá erro, é uma boa prática sempre qualificar colunas em JOINs.
INNER JOIN para responder perguntas de negócio
Exemplo 1: receita por estado (somente pedidos com cliente)
Uma pergunta comum: “Quanto vendemos por estado?” Para isso, você precisa do valor do pedido (em orders) e do estado do cliente (em customers).
SELECT c.state, SUM(o.total_amount) AS revenue FROM orders o INNER JOIN customers c ON o.customer_id = c.customer_id GROUP BY c.state ORDER BY revenue DESC;Por que INNER JOIN faz sentido aqui: se houver pedidos sem cliente correspondente, eles seriam dados inválidos para “receita por estado”, já que não dá para atribuir um estado. O INNER JOIN elimina esses casos automaticamente.
Exemplo 2: pedidos por cliente (ranking)
SELECT c.customer_id, c.full_name, COUNT(*) AS orders_count, SUM(o.total_amount) AS total_spent FROM customers c INNER JOIN orders o ON o.customer_id = c.customer_id GROUP BY c.customer_id, c.full_name ORDER BY total_spent DESC;Note que aqui a tabela da esquerda é customers. Isso muda o significado: com INNER JOIN, você verá apenas clientes que têm pelo menos um pedido. Se você quiser listar também clientes sem pedidos, entramos no LEFT JOIN.
Passo a passo: LEFT JOIN na prática (clientes com e sem pedidos)
Passo 1: defina a pergunta e o “universo”
Pergunta: “Quais clientes existem no cadastro e quantos pedidos cada um fez, incluindo quem não comprou?” O universo é “todos os clientes”, então customers deve ficar à esquerda.
Passo 2: use LEFT JOIN para não perder clientes sem pedidos
SELECT c.customer_id, c.full_name, COUNT(o.order_id) AS orders_count FROM customers c LEFT JOIN orders o ON o.customer_id = c.customer_id GROUP BY c.customer_id, c.full_name ORDER BY orders_count DESC;Detalhe importante: use COUNT(o.order_id) (ou outra coluna da tabela da direita) para contar apenas pedidos existentes. Se você usar COUNT(*), o cliente sem pedidos ainda gerará uma linha (por causa do LEFT JOIN) e pode contar como 1 dependendo do banco e do agrupamento, distorcendo o resultado. Contar uma coluna da tabela da direita evita isso porque ela será NULL quando não houver correspondência.
Passo 3: identifique clientes sem pedidos
Um uso clássico do LEFT JOIN é encontrar “faltantes”. Aqui, clientes sem pedidos:
SELECT c.customer_id, c.full_name, c.created_at FROM customers c LEFT JOIN orders o ON o.customer_id = c.customer_id WHERE o.order_id IS NULL;Essa consulta é muito útil para ações de CRM: onboarding, campanhas de primeira compra, limpeza de cadastro, etc.
LEFT JOIN para enriquecer pedidos com pagamentos (e manter pedidos sem pagamento)
Cenário: nem todo pedido está pago ainda
Em muitos negócios, o pedido nasce antes do pagamento ser confirmado. Se você fizer INNER JOIN entre pedidos e pagamentos, você verá apenas pedidos que já têm pagamento registrado. Para relatórios operacionais (fila de cobrança, pedidos pendentes), isso é um problema. A solução é LEFT JOIN com orders à esquerda.
SELECT o.order_id, o.order_date, o.total_amount, o.status AS order_status, p.payment_id, p.payment_date, p.method, p.status AS payment_status FROM orders o LEFT JOIN payments p ON p.order_id = o.order_id;Agora você tem uma visão completa: pedidos pagos e não pagos. Linhas sem pagamento terão colunas de payments como NULL.
Encontrar pedidos sem pagamento
SELECT o.order_id, o.order_date, o.total_amount FROM orders o LEFT JOIN payments p ON p.order_id = o.order_id WHERE p.payment_id IS NULL;Esse padrão (LEFT JOIN + WHERE coluna_da_direita IS NULL) é um dos mais úteis no dia a dia para auditoria e acompanhamento de processos.
Junções em cadeia: customers + orders + payments
Em análises reais, você frequentemente precisa de mais de duas tabelas. A lógica é a mesma: você vai encadeando JOINs, sempre pensando no “universo” que quer preservar e no risco de perder linhas.
Exemplo: relatório de pedidos com cliente e status de pagamento
Objetivo: ver cada pedido, o cliente e, se existir, o pagamento mais básico (sem entrar em deduplicação ainda).
SELECT o.order_id, o.order_date, o.total_amount, c.full_name, c.state, p.status AS payment_status, p.method FROM orders o INNER JOIN customers c ON c.customer_id = o.customer_id LEFT JOIN payments p ON p.order_id = o.order_id;Interpretação: você exige que todo pedido tenha um cliente válido (INNER JOIN com customers), mas não exige que tenha pagamento (LEFT JOIN com payments). Esse tipo de combinação é comum: INNER JOIN para dimensões obrigatórias (cliente, produto) e LEFT JOIN para eventos opcionais (pagamento, entrega, devolução).
Armadilha prática: multiplicação de linhas (quando há mais de um registro na tabela da direita)
Um ponto crítico em JOINs é entender a cardinalidade. Se um pedido pode ter mais de um pagamento (por exemplo, tentativa recusada e depois aprovado, ou pagamento dividido), então orders (1) para payments (N). Ao fazer JOIN, cada pagamento vira uma linha, e o pedido aparece repetido. Isso pode inflar somas e contagens se você não estiver atento.
Exemplo do problema: somar total de pedidos após juntar pagamentos
Se você fizer:
SELECT SUM(o.total_amount) AS revenue FROM orders o LEFT JOIN payments p ON p.order_id = o.order_id;Você corre o risco de somar o mesmo pedido várias vezes caso existam múltiplos pagamentos por pedido. Em relatórios financeiros, isso é uma fonte comum de divergência.
Como lidar: agregue antes de juntar (pré-agregação)
Uma abordagem robusta é transformar payments em uma tabela agregada por pedido antes do JOIN, por exemplo somando pagamentos aprovados.
SELECT o.order_id, o.total_amount, COALESCE(p.paid_amount, 0) AS paid_amount FROM orders o LEFT JOIN ( SELECT order_id, SUM(amount) AS paid_amount FROM payments WHERE status = 'approved' GROUP BY order_id ) p ON p.order_id = o.order_id;Assim, você garante no máximo uma linha de pagamento por pedido no resultado, evitando multiplicação.
Boas práticas de legibilidade e manutenção em JOINs
Use aliases consistentes
Padronize aliases curtos e previsíveis: c para customers, o para orders, p para payments. Isso reduz ruído e facilita revisão.
Selecione colunas explicitamente
Evite SELECT * em JOINs. Além de trazer colunas desnecessárias, você pode ter colisão de nomes e aumentar custo de processamento. Selecione apenas o que será usado no relatório.
Qualifique colunas com o alias
Mesmo quando não é obrigatório, escrever o.order_date em vez de order_date torna a consulta mais clara e evita erros quando a consulta cresce.
Escolha conscientemente a tabela da esquerda
No LEFT JOIN, a tabela da esquerda define o que “não pode sumir”. Antes de escrever, responda: “qual é o conjunto de registros que eu quero garantir no resultado?” Se a resposta for “todos os clientes”, customers fica à esquerda. Se for “todos os pedidos”, orders fica à esquerda.
Checklist rápido para decidir entre INNER JOIN e LEFT JOIN
- Quero apenas registros com correspondência nas duas tabelas? Use INNER JOIN.
- Quero manter todos os registros da tabela principal e apenas complementar quando existir? Use LEFT JOIN.
- Estou tentando encontrar “quem não tem” (sem pedido, sem pagamento, sem cadastro relacionado)? Use LEFT JOIN +
WHEREcom coluna da direitaIS NULL. - Existe chance de 1 para N na tabela da direita? Cuidado com duplicação; considere pré-agregar antes do JOIN.
Exercícios práticos guiados (para fixar)
1) Listar pedidos com nome do cliente (INNER JOIN)
Objetivo: gerar um relatório simples para atendimento identificar rapidamente quem fez cada pedido.
SELECT o.order_id, o.order_date, o.status, o.total_amount, c.full_name FROM orders o INNER JOIN customers c ON c.customer_id = o.customer_id ORDER BY o.order_date DESC;2) Listar todos os clientes e a data do último pedido (LEFT JOIN + agregação)
Objetivo: identificar clientes inativos e clientes novos sem compra.
SELECT c.customer_id, c.full_name, MAX(o.order_date) AS last_order_date FROM customers c LEFT JOIN orders o ON o.customer_id = c.customer_id GROUP BY c.customer_id, c.full_name ORDER BY last_order_date DESC;3) Pedidos pendentes de pagamento (LEFT JOIN + IS NULL)
Objetivo: fila de cobrança ou conciliação.
SELECT o.order_id, o.order_date, o.total_amount FROM orders o LEFT JOIN payments p ON p.order_id = o.order_id WHERE p.order_id IS NULL ORDER BY o.order_date;4) Total pago por pedido (pré-agregação para evitar duplicação)
Objetivo: comparar valor do pedido vs valor efetivamente pago.
SELECT o.order_id, o.total_amount, COALESCE(p.paid_amount, 0) AS paid_amount, (o.total_amount - COALESCE(p.paid_amount, 0)) AS remaining_amount FROM orders o LEFT JOIN ( SELECT order_id, SUM(amount) AS paid_amount FROM payments WHERE status = 'approved' GROUP BY order_id ) p ON p.order_id = o.order_id ORDER BY remaining_amount DESC;