Capa do Ebook gratuito SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

SQL para Análise de Dados no Dia a Dia: Consultas, Relatórios e Insights com Dados Reais

Novo curso

26 páginas

Junções na prática com INNER JOIN e LEFT JOIN usando dados reais fictícios

Capítulo 4

Tempo estimado de leitura: 0 minutos

+ Exercício

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...
Download App

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 + WHERE com coluna da direita IS 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;

Agora responda o exercício sobre o conteúdo:

Em um relatório que deve listar todos os pedidos e, quando existir, trazer informações de pagamento (mantendo pedidos ainda não pagos), qual abordagem de junção é a mais adequada?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

O LEFT JOIN com orders à esquerda mantém todos os pedidos no resultado e só preenche os dados de payments quando existir correspondência; pedidos sem pagamento ficam com campos de pagamento como NULL.

Próximo capitúlo

Chaves, cardinalidade e duplicidades: como evitar resultados inflados em JOINs

Arrow Right Icon
Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.