Self JOIN: uma tabela se juntando a si mesma
1. O Conceito de Self JOIN
O Self JOIN é uma técnica poderosa em SQL onde uma tabela é combinada com ela mesma. Diferentemente dos JOINs tradicionais que conectam duas tabelas distintas, o Self JOIN utiliza uma única tabela, mas a trata como se fossem duas instâncias separadas através do uso de aliases.
A necessidade do Self JOIN surge quando precisamos estabelecer relacionamentos dentro da mesma entidade. Os casos mais comuns envolvem estruturas hierárquicas (como um funcionário que se reporta a outro funcionário) ou comparações internas (como encontrar pares de registros que compartilham alguma característica).
A principal diferença para JOINs tradicionais é que, enquanto estes conectam tabelas diferentes (ex: clientes com pedidos), o Self JOIN explora relacionamentos que existem dentro de uma única tabela, geralmente através de uma chave estrangeira que referencia a própria chave primária.
2. Sintaxe Básica e Alias de Tabela
A sintaxe fundamental do Self JOIN segue este padrão:
SELECT A.coluna1, B.coluna2
FROM tabela AS A
JOIN tabela AS B ON A.chave = B.chave_estrangeira;
Os aliases (neste caso A e B) são obrigatórios. Sem eles, o SQL não conseguiria distinguir qual instância da tabela estamos referenciando em cada parte da consulta.
Vejamos um exemplo mínimo com uma tabela de funcionários:
CREATE TABLE funcionarios (
id INT PRIMARY KEY,
nome VARCHAR(100),
gerente_id INT
);
INSERT INTO funcionarios VALUES
(1, 'Ana', NULL),
(2, 'Carlos', 1),
(3, 'Maria', 1),
(4, 'João', 2);
-- Listar funcionários com seus gerentes
SELECT
F.nome AS funcionario,
G.nome AS gerente
FROM funcionarios AS F
JOIN funcionarios AS G ON F.gerente_id = G.id;
Resultado:
funcionario | gerente
Carlos | Ana
Maria | Ana
João | Carlos
3. Casos de Uso Clássicos – Hierarquias
Estruturas organizacionais são o exemplo mais clássico de Self JOIN. Cada funcionário pode ter um gerente que também é um funcionário registrado na mesma tabela.
-- Estrutura organizacional completa
SELECT
F.nome AS funcionario,
F.cargo,
G.nome AS gerente,
G.cargo AS cargo_gerente
FROM funcionarios AS F
JOIN funcionarios AS G ON F.gerente_id = G.id
ORDER BY G.nome, F.nome;
Outro caso comum são árvores de categorias:
CREATE TABLE categorias (
id INT PRIMARY KEY,
nome VARCHAR(100),
categoria_pai_id INT
);
INSERT INTO categorias VALUES
(1, 'Eletrônicos', NULL),
(2, 'Computadores', 1),
(3, 'Smartphones', 1),
(4, 'Notebooks', 2),
(5, 'Tablets', 2);
-- Listar subcategorias com suas categorias pai
SELECT
C.nome AS subcategoria,
P.nome AS categoria_pai
FROM categorias AS C
JOIN categorias AS P ON C.categoria_pai_id = P.id;
4. Casos de Uso Clássicos – Comparações Internas
O Self JOIN também é excelente para encontrar pares de registros que compartilham características:
-- Encontrar clientes que moram na mesma cidade
SELECT
A.nome AS cliente1,
B.nome AS cliente2,
A.cidade
FROM clientes AS A
JOIN clientes AS B ON A.cidade = B.cidade AND A.id < B.id;
A condição A.id < B.id é crucial para evitar pares espelhados (João-Maria e Maria-João) e também para evitar que um cliente seja pareado consigo mesmo.
Outro exemplo prático:
-- Filmes lançados no mesmo ano
SELECT
F1.titulo AS filme1,
F2.titulo AS filme2,
F1.ano
FROM filmes AS F1
JOIN filmes AS F2 ON F1.ano = F2.ano AND F1.id < F2.id
ORDER BY F1.ano, F1.titulo;
5. Self JOIN com LEFT JOIN
Quando trabalhamos com hierarquias, frequentemente precisamos incluir registros que não têm correspondência (como o CEO que não tem gerente):
-- Todos os funcionários, incluindo o topo da hierarquia
SELECT
F.nome AS funcionario,
COALESCE(G.nome, 'Sem gerente') AS gerente
FROM funcionarios AS F
LEFT JOIN funcionarios AS G ON F.gerente_id = G.id;
A diferença entre INNER e LEFT JOIN em Self JOIN é significativa:
- INNER JOIN: retorna apenas registros com correspondência (funcionários que têm gerente)
- LEFT JOIN: retorna todos os registros da tabela esquerda, mesmo sem correspondência
-- Comparando INNER vs LEFT
SELECT 'INNER' AS tipo, F.nome, G.nome AS gerente
FROM funcionarios AS F
JOIN funcionarios AS G ON F.gerente_id = G.id
UNION ALL
SELECT 'LEFT', F.nome, COALESCE(G.nome, 'NULL')
FROM funcionarios AS F
LEFT JOIN funcionarios AS G ON F.gerente_id = G.id;
6. Self JOIN com Filtros e Condições Avançadas
Condições mais sofisticadas permitem análises interessantes:
-- Encontrar pares de produtos com preços próximos (diferença menor que 10%)
SELECT
P1.nome AS produto1,
P2.nome AS produto2,
P1.preco,
P2.preco,
ROUND(ABS(P1.preco - P2.preco) / P1.preco * 100, 2) AS diferenca_percentual
FROM produtos AS P1
JOIN produtos AS P2
ON P1.categoria_id = P2.categoria_id
AND P1.id < P2.id
AND ABS(P1.preco - P2.preco) / P1.preco < 0.10
ORDER BY diferenca_percentual;
A combinação de múltiplas condições no JOIN evita a necessidade de WHERE e pode melhorar o desempenho:
-- Funcionários que ganham mais que seus gerentes
SELECT
F.nome AS funcionario,
F.salario,
G.nome AS gerente,
G.salario AS salario_gerente
FROM funcionarios AS F
JOIN funcionarios AS G ON F.gerente_id = G.id
WHERE F.salario > G.salario;
7. Self JOIN vs. Subconsultas e CTEs
Para hierarquias simples de um nível, o Self JOIN é geralmente mais eficiente e legível que subconsultas correlacionadas:
-- Self JOIN (mais eficiente)
SELECT F.nome, G.nome AS gerente
FROM funcionarios F
JOIN funcionarios G ON F.gerente_id = G.id;
-- Subconsulta correlacionada (menos eficiente)
SELECT
F.nome,
(SELECT nome FROM funcionarios WHERE id = F.gerente_id) AS gerente
FROM funcionarios F;
Para hierarquias de múltiplos níveis, CTEs recursivas são mais adequadas:
-- Hierarquia completa com Self JOIN (apenas 1 nível)
SELECT F.nome, G.nome AS gerente
FROM funcionarios F
LEFT JOIN funcionarios G ON F.gerente_id = G.id;
-- Hierarquia completa com CTE recursiva (todos os níveis)
WITH RECURSIVE hierarquia AS (
SELECT id, nome, gerente_id, 1 AS nivel
FROM funcionarios
WHERE gerente_id IS NULL
UNION ALL
SELECT F.id, F.nome, F.gerente_id, H.nivel + 1
FROM funcionarios F
JOIN hierarquia H ON F.gerente_id = H.id
)
SELECT * FROM hierarquia;
A escolha depende da profundidade da hierarquia: Self JOIN para um nível, CTE recursiva para múltiplos níveis.
8. Armadilhas e Boas Práticas
Armadilha 1: Junções Cartesianas Acidentais
-- PERIGO: Isso gera um produto cartesiano!
SELECT A.nome, B.nome
FROM funcionarios A, funcionarios B;
-- CORRETO: Sempre especifique a condição de JOIN
SELECT A.nome, B.nome
FROM funcionarios A
JOIN funcionarios B ON A.gerente_id = B.id;
Armadilha 2: Pares Espelhados e Auto-relacionamento
-- Evite pares espelhados e auto-relacionamento
SELECT A.nome, B.nome
FROM clientes A
JOIN clientes B ON A.cidade = B.cidade AND A.id < B.id;
-- A condição A.id < B.id é essencial!
Boas Práticas:
- Índices são cruciais: Crie índices nas colunas usadas na condição de JOIN
CREATE INDEX idx_gerente ON funcionarios(gerente_id);
- Nomenclatura clara de aliases: Use letras que façam sentido
-- Bom
FROM funcionarios AS func
JOIN funcionarios AS ger ON func.gerente_id = ger.id
-- Melhor ainda em tabelas grandes
FROM funcionarios AS subordinado
JOIN funcionarios AS superior ON subordinado.gerente_id = superior.id
-
Sempre teste com dados pequenos primeiro antes de aplicar em produção
-
Documente o propósito do Self JOIN em consultas complexas
O Self JOIN é uma ferramenta indispensável no arsenal de qualquer profissional de banco de dados. Dominá-lo permite modelar relacionamentos hierárquicos e realizar comparações internas com elegância e eficiência, transformando uma única tabela em uma fonte rica de insights relacionais.
Referências
- Documentação PostgreSQL: Self-Joins — Documentação oficial do PostgreSQL explicando a sintaxe e uso de Self JOINs
- W3Schools SQL Self JOIN — Tutorial interativo com exemplos práticos de Self JOIN
- SQL Server Self Join (Microsoft Learn) — Documentação da Microsoft sobre Self JOINs no SQL Server
- Mode Analytics SQL Tutorial: Self Joins — Tutorial completo com exemplos do mundo real e visualizações
- SQLShack: Understanding Self Joins — Artigo técnico detalhado com casos de uso avançados e dicas de performance
- GeeksforGeeks: SQL Self JOIN — Explicação clara com exemplos de hierarquias e comparações internas