DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

SQL: JOIN Queries

Introdução

Bancos de dados relacionais distribuem informações em múltiplas tabelas de propósito — para evitar redundância e garantir integridade. Mas na hora de consultar, frequentemente precisamos de dados que estão espalhados por duas ou mais tabelas. É aí que entram os JOINs: operações que combinam linhas de tabelas distintas com base em uma condição de correspondência.

Para todos os exemplos, usaremos estas duas tabelas:

clientes:
| id | nome        | cidade         |
|----|-------------|----------------|
|  1 | Ana Lima    | São Paulo      |
|  2 | Bruno Melo  | Curitiba       |
|  3 | Carla Nunes | Rio de Janeiro |
|  4 | Diego Costa | Brasília       |

pedidos:
| id | cliente_id | produto  | valor   |
|----|------------|----------|---------|
|  1 | 1          | Notebook | 3500.00 |
|  2 | 1          | Mouse    |   80.00 |
|  3 | 2          | Teclado  |  150.00 |
|  4 | 99         | Monitor  |  900.00 |
Enter fullscreen mode Exit fullscreen mode

Dois detalhes importantes: Diego (id=4) não tem nenhum pedido. E o pedido 4 referencia o cliente_id 99, que não existe na tabela de clientes. Esses casos-limite vão revelar o comportamento de cada tipo de JOIN.

INNER JOIN

Retorna apenas as linhas que têm correspondência nos dois lados. Linhas sem par são descartadas.

SELECT
  c.nome,
  p.produto,
  p.valor
FROM clientes c
INNER JOIN pedidos p ON p.cliente_id = c.id;
Enter fullscreen mode Exit fullscreen mode
| nome       | produto  | valor   |
|------------|----------|---------|
| Ana Lima   | Notebook | 3500.00 |
| Ana Lima   | Mouse    |   80.00 |
| Bruno Melo | Teclado  |  150.00 |
Enter fullscreen mode Exit fullscreen mode

Diego desapareceu — não tem pedidos. O pedido 4 (cliente_id=99) também desapareceu — não tem cliente correspondente. O INNER JOIN é o mais restritivo: só retorna o que existe nos dois lados.

É o tipo de JOIN mais comum, e a palavra INNER é opcional — JOIN sozinho já implica INNER JOIN.

LEFT JOIN

Retorna todas as linhas da tabela da esquerda, com os dados da tabela da direita onde houver correspondência. Onde não houver, os campos da direita vêm como NULL.

SELECT
  c.nome,
  p.produto,
  p.valor
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id= c.id;
Enter fullscreen mode Exit fullscreen mode
| nome        | produto  | valor   |
|-------------|----------|---------|
| Ana Lima    | Notebook | 3500.00 |
| Ana Lima    | Mouse    |   80.00 |
| Bruno Melo  | Teclado  |  150.00 |
| Carla Nunes | NULL     | NULL    |
| Diego Costa | NULL     | NULL    |
Enter fullscreen mode Exit fullscreen mode

Agora Diego e Carla aparecem — sem pedidos, mas presentes. O LEFT JOIN é ideal para perguntas do tipo: "quais clientes ainda não fizeram nenhum pedido?"

SELECT nome
FROM clientes c
LEFT JOIN pedidos p ON p.cliente_id= c.id
WHERE p.id IS NULL;
Enter fullscreen mode Exit fullscreen mode
| nome        |
|-------------|
| Carla Nunes |
| Diego Costa |
Enter fullscreen mode Exit fullscreen mode

Filtrar por IS NULL no lado direito após um LEFT JOIN é um padrão clássico para encontrar registros sem correspondência.

RIGHT JOIN

O espelho do LEFT JOIN: retorna todas as linhas da tabela da direita, com os dados da esquerda onde houver correspondência.

SELECT
  c.nome,
  p.produto,
  p.valor
FROM clientes c
RIGHT JOIN pedidos p ON p.cliente_id= c.id;
Enter fullscreen mode Exit fullscreen mode
| nome       | produto  | valor   |
|------------|----------|---------|
| Ana Lima   | Notebook | 3500.00 |
| Ana Lima   | Mouse    |   80.00 |
| Bruno Melo | Teclado  |  150.00 |
| NULL       | Monitor  |  900.00 |
Enter fullscreen mode Exit fullscreen mode

Agora o pedido 4 aparece — com NULL no nome do cliente, pois o cliente_id=99 não existe. Diego e Carla voltam a sumir.

Na prática, RIGHT JOIN é pouco usado — qualquer RIGHT JOIN pode ser reescrito como LEFT JOIN invertendo a ordem das tabelas, o que muitos consideram mais legível. Ele existe por simetria e completude da linguagem.

FULL OUTER JOIN

Combina LEFT e RIGHT: retorna todas as linhas dos dois lados, com NULL onde não houver correspondência.

SELECT
  c.nome,
  p.produto,
  p.valor
FROM clientes c
FULL OUTER JOIN pedidos p ON p.cliente_id= c.id;
Enter fullscreen mode Exit fullscreen mode
| nome        | produto  | valor   |
|-------------|----------|---------|
| Ana Lima    | Notebook | 3500.00 |
| Ana Lima    | Mouse    |   80.00 |
| Bruno Melo  | Teclado  |  150.00 |
| Carla Nunes | NULL     | NULL    |
| Diego Costa | NULL     | NULL    |
| NULL        | Monitor  |  900.00 |
Enter fullscreen mode Exit fullscreen mode

Ninguém é descartado. Clientes sem pedidos aparecem, pedidos sem cliente aparecem. É útil para auditorias e para encontrar inconsistências nos dois lados de uma relação.

Vale saber: o MySQL não suporta FULL OUTER JOIN nativamente. O equivalente é unir um LEFT JOIN com um RIGHT JOIN usando UNION.

Self Join

Um Self Join é quando uma tabela é juntada com ela mesma. Não é um tipo de JOIN diferente — é uma aplicação de qualquer JOIN (geralmente INNER ou LEFT) usando aliases para tratar a mesma tabela como se fossem duas.

O caso de uso mais clássico: uma tabela de funcionários onde cada funcionário tem um gerente_id que referencia outro funcionário na mesma tabela.

funcionarios:
| id | nome       | gerente_id |
|----|------------|------------|
|  1 | Carlos     | NULL       |
|  2 | Ana        | 1          |
|  3 | Bruno      | 1          |
|  4 | Diego      | 2          |
Enter fullscreen mode Exit fullscreen mode
SELECT
  f.nome AS funcionario,
  g.nome AS gerente
FROM funcionarios f
LEFT JOIN funcionarios gON f.gerente_id= g.id;
Enter fullscreen mode Exit fullscreen mode
| funcionario | gerente |
|-------------|---------|
| Carlos      | NULL    |
| Ana         | Carlos  |
| Bruno       | Carlos  |
| Diego       | Ana     |
Enter fullscreen mode Exit fullscreen mode

O alias é obrigatório aqui: f representa o funcionário e g representa o gerente — ambos vindos da mesma tabela. Sem aliases, o banco não saberia a qual instância da tabela cada coluna pertence.

Cross Join

CROSS JOIN produz o produto cartesiano das duas tabelas: cada linha de A é combinada com cada linha de B. Se A tem 4 linhas e B tem 3, o resultado tem 12 linhas. Não tem cláusula ON — não há condição de correspondência.

SELECT
  c.nome,
  p.produto
FROM clientes c
CROSS JOIN pedidos p;
Enter fullscreen mode Exit fullscreen mode

Isso retornaria 4 × 4 = 16 combinações, incluindo Ana com Monitor e Diego com Notebook — independente de qualquer relação real entre eles.

Na maioria dos casos, um CROSS JOIN acidental em tabelas grandes é catastrófico para a performance. 1.000 linhas × 1.000 linhas = 1 milhão de resultados. Mas há casos de uso legítimos: gerar combinações para testes, preencher uma grade de horários, ou calcular todas as possibilidades de configuração de um produto.

Resumo Visual

Tabela A    Tabela B

INNER JOIN  → apenas a intersecção (correspondência nos dois lados)
LEFT JOIN   → tudo de A + correspondências de B
RIGHT JOIN  → tudo de B + correspondências de A
FULL OUTER  → tudo de A + tudo de B
CROSS JOIN  → todas as combinações possíveis
SELF JOIN   → a tabela junta com ela mesma
Enter fullscreen mode Exit fullscreen mode

JOINs em cadeia

Na prática, consultas reais combinam múltiplos JOINs:

SELECT
  c.nomeAS cliente,
  p.idAS pedido,
  pr.nomeAS produto,
  ip.quantidade,
  ip.quantidade* pr.precoAS subtotal
FROM clientes c
INNER JOIN pedidos pON p.cliente_id= c.id
INNER JOIN itens_pedido ipON ip.pedido_id= p.id
INNER JOIN produtos prON pr.id= ip.produto_id
WHERE c.id= 1
ORDER BY p.id;
Enter fullscreen mode Exit fullscreen mode

Cada JOIN adiciona uma tabela ao contexto da consulta. A ordem importa para legibilidade, mas não para o resultado — o otimizador do banco decide a ordem de execução internamente, independente da ordem em que os JOINs foram escritos.

Top comments (0)