Voltar para a lista de artigos Artigos
11 minutos de leitura

O Que é um Self Join em SQL? Sete Exemplos para Explicar

A auto-junção ("Self Join") é um caso especial comum de JOIN em SQL. Enquanto a maioria dos JOINs liga duas ou mais tabelas entre si para apresentar seus dados de forma combinada, um Self Join liga uma tabela a ela mesma. Isto geralmente é feito ligando uma tabela a si mesma apenas uma vez dentro de uma consulta SQL, mas é possível fazê-lo várias vezes dentro da mesma consulta.

Normalmente, cada tabela em um banco de dados armazena um tipo específico de informação. Assim, um banco de dados geralmente possui centenas de tabelas relacionadas entre si. Isto implica a necessidade de junções ou ligações (ou joins, em inglês). Você pode unir diferentes tabelas por suas colunas comuns usando a palavra-chave JOIN. Também é possível unir uma tabela a si mesma, o que é conhecido como Self Join (ou auto-junção). Neste artigo, discutiremos o que é um Self Join, como ele funciona e quando você precisa usá-lo em suas consultas SQL.

Para praticar o uso de JOINs em SQL, incluindo Self Joins, recomendo nosso curso Cláusulas JOIN em SQL. Ele contém mais de 90 exercícios sobre diferentes tipos de JOINs, incluindo seções dedicadas apenas ao Self Join.

O que é um Self Join em SQL?

O Self Join, como o próprio nome sugere, liga uma tabela a ela mesma. Para utilizar um Self Join, a tabela deve conter uma coluna (vamos chamá-la de X) que atua como chave primária e uma coluna diferente (chamada de Y) que armazena valores que podem ser combinados com os valores da coluna X. Os valores das colunas X e Y não precisam ser os mesmos para uma determinada linha, e o valor da coluna Y pode até ser vazio (NULL).

Vamos conferir um exemplo. Considere a tabela funcionários:

Idnome_completosalarioid_gerente
1João Silva100003
2Júlia Almeida120003
3Antônio Luís150004
4Anna Castro20000
5Jonatan Reis90001

Cada funcionário tem seu próprio Id, que é nossa "Coluna X". Para um determinado funcionário (isto é, linha), a coluna id_gerente contém o Id de seu gerente; esta é a nossa "Coluna Y". Se rastrearmos os pares funcionário-gerente nesta tabela usando estas colunas:

  • O gerente do funcionário João Silva é o funcionário com Id 3, ou seja, Antônio Luís.
  • O gerente da funcionária Júlia Almeida é o funcionário com Id 3, ou seja, Antônio Luís.
  • A gerente do funcionário Antônio Luís é a funcionária com Id 4, ou seja, Anna Castro.
  • A funcionária Anna Castro não tem um gerente; seu id_gerente é nulo.
  • O gerente do funcionário Jonatan Reis é o funcionário com Id 1, ou seja, João Silva.

Este tipo de estrutura de tabela é muito comum em hierarquias. Agora, para mostrar o nome do gerente para cada funcionário em uma mesma linha, podemos fazer a seguinte consulta:

SELECT
	funcionário.Id,
    	funcionário.nome_completo,
    	funcionário.id_gerente,
    	gerente.nome_completo as nome_gerente
FROM funcionários funcionário
JOIN funcionários gerente
ON funcionário.id_gerente = gerente.Id

que retorna o seguinte resultado:

Idnome_completoid_gerentenome_gerente
1João Silva3Antônio Luís
2Júlia Almeida3Antônio Luís
3Antônio Luís4Anna Castro
5Jonatan Reis1João Silva

A consulta seleciona as colunas Id, nome_completo e id_gerente da tabela funcionários. Ele também seleciona a coluna nome_completo da tabela, também conhecida como gerente e designa esta coluna como nome_gerente. Como resultado, cada funcionário que tem um gerente é exibido junto com o ID e o nome de seu gerente.

Nesta consulta, a tabela funcionários está unida a si mesma e tem dois papéis diferentes:

  • Função 1: Armazena os dados dos funcionários (por isso, é chamada funcionário).
  • Função 2: Armazena os dados do gerente (por isso, é chamada gerente).

Ao fazer isso, estamos essencialmente considerando as duas cópias da tabela funcionários como se fossem duas tabelas distintas, uma para os funcionários e outra para os gerentes.

Você pode saber mais sobre o conceito de Self Join (auto-junção) em nosso artigo Um Guia Ilustrado sobre Self Joins em SQL.

Tabela com Pseudônimos em Self Join

Quando nos referimos à mesma tabela mais de uma vez em uma consulta SQL, precisamos de uma maneira para diferenciar cada vez que nos referimos a uma delas. Por isso, é importante usar pseudônimos (apelidos) para identificar de forma única cada referência de uma única tabela em uma consulta SQL. Como uma boa prática, os pseudônimos devem indicar o papel da tabela para cada referência específica em uma consulta SQL.

Os pseudônimos aparecem em vermelho na consulta a seguir. Você pode ver a declaração deles nas cláusulas FROM e JOIN.

SELECT
	funcionário.Id,
    	funcionário.nome_completo,
    	funcionário.id_gerente,
    	gerente.nome_completo as nome_gerente
FROM funcionários funcionário
JOIN funcionários gerente
ON funcionário.id_gerente = gerente.Id

A palavra-chave JOIN conecta duas tabelas e geralmente é seguida por uma cláusula ON ou USING, que especifica as colunas comuns usadas para juntar as duas tabelas. Aqui, vemos que as duas referências à tabela funcionários são unidas por uma condição do id_gerente do funcionário para corresponder à identificação do funcionário do gerente.

Exemplos

Vamos passar por alguns cenários comuns que utilizam Self Joins.

Cenário 1: Processamento de uma Hierarquia em SQL

A auto-junção é comumente usada para processar uma hierarquia. Como vimos anteriormente, uma hierarquia atribui uma linha em uma tabela à uma outra linha dentro da mesma tabela. Pense nisso como linhas de pais e filhos.

Voltemos ao exemplo com os funcionários e seus gerentes. Aqui está mais uma vez a tabela funcionários:

Idnome_completosalarioid_gerente
1João Silva100003
2Júlia Almeida120003
3Antônio Luís150004
4Anna Castro20000João Silva
5Jonatan Reis90001

E o código para listar cada funcionário que tem um gerente com o nome de seu gerente:

SELECT
	funcionário.Id,
    	funcionário.nome_completo,
    	funcionário.id_gerente,
    	gerente.nome_completo as nome_gerente
FROM funcionários funcionário
JOIN funcionários gerente
ON funcionário.id_gerente = gerente.Id

Aqui está o resultado quando você executa o código:

Idnome_completoid_gerentenome_gerente
1João Silva3Antônio Luís
2Júlia Almeida3Antônio Luís
3Antônio Luís4Anna Castro
5Jonatan Reis1João Silva

Esta consulta utiliza o JOIN padrão, também conhecido como INNER JOIN. Para ler mais sobre o INNER JOIN, consulte nosso artigo Guia Ilustrado sobre o INNER JOIN em SQL.

Se quisermos listar todos os funcionários, com ou sem gerentes, podemos usar um LEFT OUTER JOIN. A consulta abaixo faz isso:

SELECT
	funcionário.Id,
    	funcionário.nome_completo,
    	funcionário.id_gerente,
    	gerente.nome_completo as nome_gerente
FROM funcionários funcionário
LEFT OUTER JOIN funcionários gerente
ON funcionário.id_gerente = gerente.Id

Ao executar esta consulta, você obtém o seguinte resultado:

Idnome_completoid_gerentenome_gerente
1João Silva3Antônio Luís
2Júlia Almeida3Antônio Luís
3Antônio Luís4Anna Castro
4Anna Castro  
5Jonatan Reis1João Silva

A diferença entre JOIN e LEFT OUTER JOIN fica clara quando comparamos este resultado com o da consulta anterior. No resultado da consulta JOIN anterior, somente os funcionários com gerentes são incluídos. Em contraste, a consulta LEFT OUTER JOIN retorna todos os funcionários, com ou sem gerentes. Para ler mais sobre OUTER JOIN, leia nosso artigo Guia Ilustrado sobre OUTER JOIN em SQL.

Outro exemplo de hierarquia é a relação entre pais e filhos. Considere a tabela humanos mostrada a seguir:

IdNomeIdadeIdentificação_pais
1Jonathan53
2Alexandra73
3Barbara30

Na consulta abaixo, os filhos são designadas a seus respectivos pais, ligando a tabela humanos a ela mesma:

SELECT
	identificacao.filhos as identificação_filhos,
    	filho.Nome as nome_filho,
    	filho.idade as idade_filho,
    	filho.identificação_pais,
    	pais.nome as nome_pais,
    	pais.idade as idade_pais
FROM humanos filho
INNER JOIN humanos pais
ON filho.identificação_pais = pais.Id

Aqui está o resultado desta consulta:

identificação_filhosnome_filhoidade_filhoidentificação_paisnome_paisidade_pais
1Jonathan53Barbara30
2Alexandra73Barbara30

O resultado da consulta inclui apenas os filhos que têm pais. Como foi o caso no exemplo da hierarquia funcionário-gerente, poderíamos usar um LEFT OUTER JOIN para incluir todas as linhas da tabela filho.

Aqui está mais um exemplo de hierarquia. Considere a tabela categoria, mostrada abaixo:

Idquantidadecategoriaid_categoriamãe
160Comida
250Frutas1
340Maçã2
420Maçã Verde3
5100Leite1
660Leite de Soja5
740Leite de Vaca5
830Leite Integral7
910Leite Desnatado7

Vamos atribuir uma categoria-mãe a cada categoria sempre que possível. Aqui está uma consulta para fazer isso:

SELECT
	categoria.Id,
	categoria.quantidade,
    	categoria.categoria,
    	categoria.id_categoriamãe,
    	categoriamãe.categoria as categoriamãe
FROM categoria categoria
JOIN categoria categoriamãe
ON categoria.id_categoriamãe = categoriamãe.Id

E aqui está o resultado:

Idquantidadecategoriaid_categoriamãecategoriamãe
250Frutas1Comida
340Maçã2Frutas
420Maçã Verde3Maçã
5100Leite1Comida
660Leite de Soja5Leite
740Leite de Vaca5Leite
830Leite Integral7Leite de Vaca
910Leite Desnatado7Leite de Vaca

As primeiras quatro colunas do resultado acima vêm da referência à tabela de pseudônimo categoria. A última coluna vem da tabela também conhecida como categoriamãe e contém o nome da categoria-mãe correspondente ao respectivo Id.

A tabela categoria tem dois papéis diferentes, como indicam as duas referências. A coluna id_categoriamãe da tabela chamada categoria é correspondida com a coluna Id da tabela de pseudônimo categoriamãe. A cláusula ON especifica que id_categoriamãe da tabela categoria deve ser igual a Id da tabela categoriamãe para conectar as linhas correspondentes.

Cenário 2: Listando pares dentro de uma tabela

Você pode usar o Self Join para gerar pares de linhas com base na condição da cláusula ON. Vamos começar com um exemplo simples que gera todos os pares possíveis entre os colegas. Considere a seguinte tabela, colegas:

Idnome_completoidade
1Bruno Tomás43
2Catarina Andrade44
3João Brito35
4Nicole Melo29

Suponhamos que precisamos gerar todos os pares possíveis entre colegas para que todos tenham a oportunidade de conversar com todos os outros na confraternização da empresa. Aqui está o código SQL:

SELECT
	membroequipe1.nome_completo as membroequipe1nome_completo,
	membroequipe1.idade as membroequipe1idade,
    	membroequipe2.nome_completo as membroequipe2nome_completo,
	membroequipe2.idade as membroequipe2idade
FROM colegas membroequipe1
CROSS JOIN colegas membroequipe2
ON membroequipe1.nome_completo <> membroequipe2.nome_completo

E aqui está o resultado:

membroequipe1nome_completomembroequipe1idademembroequipe2nome_completomembroequipe2idade
Catarina Andrade44Bruno Tomás43
João Brito35Bruno Tomás43
Nicole Melo29Bruno Tomás43
Bruno Tomás43Catarina Andrade44
João Brito35Catarina Andrade44
Nicole Melo29Catarina Andrade44
Bruno Tomás43João Brito35
Catarina Andrade44João Brito35
Nicole Melo29João Brito35
Bruno Tomás43Nicole Melo29
Catarina Andrade44Nicole Melo29
João Brito35Nicole Melo29

O resultado conecta cada pessoa a todas as outras pessoas da tabela. Como não queremos que ninguém seja pareado com si próprio, temos a condição da cláusula ON membroequipe1.nome_completo <> membroequipe2.nome_completo . Isto significa que cada pessoa será pareada com três outros colegas, pois há quatro colegas neste evento.

Agora, vamos conferir um exemplo um pouco mais complicado. Considere a tabela humano mostrada abaixo. Queremos criar correspondências entre os antepassados de cada pessoa nos casos em que os dados permitem, onde uma pessoa é considerada um antepassado se ele ou ela tiver um valor de Id mais alto.

Abaixo está a tabela humano utilizada neste exemplo.

Idprimeiro_nomeidadeidentificação_pais
1Jonathan53
2Alexandra73
3Barbara304
4Antônio506
5Jorge556
6Amanda807
7Joana9935

Vamos encontrar todos os pares descendente-antepassado existentes na tabela acima. Aqui está o código SQL:

SELECT
	descendente.Id,
    	descendente.primeiro_nome,
    	descendente.idade,
    	descendente.identificação_pais,
    	antepassado.Id as identidade_antepassado,
    	antepassado.primeiro_nome as primeiro_nome_antepassado,
    	antepassado.idade as idade_antepassados
FROM humano descendente
LEFT JOIN humano antepassado
ON descendente.identificação_pais <= antepassado.Id

E o resultado:

Idprimeiro_nomeidadeidentificação_paisidentidade_antepassadoprimeiro_nome_antepassadoidade_antepassados
1Jonathan533Barbara30
1Jonathan534Antônio50
1Jonathan535Jorge55
1Jonathan536Amanda80
1Jonathan537Joana99
2Alexandra733Barbara30
2Alexandra734Antônio50
2Alexandra735Jorge55
2Alexandra736Amanda80
2Alexandra737Joana99
3Barbara3044Antônio50
3Barbara3045Jorge55
3Barbara3046Amanda80
3Barbara3047Joana99
4Antônio5066Amanda80
4Antônio5067Joana99
5Jorge5566Amanda80
5Jorge5567Joana99
6Amanda8077Joana99
7Joana99

Ao especificar a condição da cláusula ON descendente.identificação_pais <= ancestral.Id , encontramos todos os antepassados de cada pessoa na tabela onde eles existem; caso contrário, a consulta retorna null para informações sobre os antepassados.

As primeiras quatro colunas são tiradas da tabela descendente, que contém informações da pessoa para a qual os antepassados são procurados. As três últimas colunas são retiradas da tabela antepassado e contém detalhes sobre cada antepassado.

Cenário 3: Fazendo um Self Join com Outra Tabela

Em SQL, é possível realizar auto-junções com uma ou mais tabelas diferentes. Embora não seja uma auto-junção "limpa", é uma prática comum.

Uma aplicação real disto é a informação de vôos nos aeroportos, com uma enorme quantidade de dados a cada hora. Imagine que queremos procurar um número de identificação de vôo junto com os detalhes sobre seus aeroportos de origem e destino. Considere as seguintes tabelas:

Tabela aeroporto:

identificação_aeroportopaíscidade
1EUANova York
2CanadáToronto
3AlemanhaFrankfurt
4FrançaParis
5ItáliaRoma

Tabela vôo:

id_vooidentificacao_aviaoinicio_rotafim_rotaidentificacao_aeroporto_inicioidentificacao_aeroporto_fim
25558772020-01-14 13:00:002020-01-14 15:00:0034
32225362020-02-04 01:00:002020-02-04 16:00:0015
41117452020-02-15 09:00:002020-02-15 12:00:0054
57775242020-02-24 03:00:002020-02-24 19:00:0042
68885212020-03-25 10:00:002020-03-25 12:00:0021
74449372020-04-01 00:00:002020-04-01 17:00:0031
2431116542020-01-01 02:00:002020-01-01 04:00:0012

Aqui, note que a coluna identificação_aeroporto da tabela aeroporto é a chave estrangeira para as colunas identificacao_aeroporto_inicio e identificacao_aeroporto_fim da tabela vôo. Vamos juntar a tabela aeroporto à tabela vôo duas vezes seguidas, desta forma:

  • No primeiro JOIN, aeroporto assume o papel da tabela com os aeroportos de origem.
  • No segundo JOIN, aeroporto assume o papel da tabela com os aeroportos de destino.

A consulta fica assim:

SELECT
	vôo.id_voo,
    	vôo.identificacao_aviao,
    	vôo.identificacao_aeroporto_inicio,
    	aeroportoinício.país as pais_aeroporto_inicio,
    	aeroportoinício.cidade as cidade_aeroporto_inicio,
    	vôo.identificacao_aeroporto_fim,
    	aeroportofim.país as pais_aeroporto_fim,
    	aeroportofim.cidade as cidade_aeroporto_fim
FROM vôo vôo
JOIN aeroporto aeroportoinício
ON vôo.identificacao_aeroporto_inicio = aeroportoinício.identificação_aeroporto
JOIN aeroporto aeroportofim
ON vôo.identificacao_aeroporto_fim = aeroportofim.identificação_aeroporto

E o resultado da consulta fica assim:

id_vooidentificacao_aviaoidentificacao_aeroporto_iniciopais_aeroporto_iniciocidade_aeroporto_inicioidentificacao_aeroporto_fimpais_aeroporto_fimcidade_aeroporto_fim
11116541EUANova York2CanadáToronto
25558773AlemanhaFrankfurt4FrançaParis
32225361EUANova York5ItáliaRoma
41117455ItáliaRoma4FrançaParis
57775244FrançaParis2CanadáToronto
68885212CanadáToronto1EUANova York
74449373AlemanhaFrankfurt1EUANova York

Vamos analisar o resultado. As três primeiras colunas vêm de um simples SELECT na tabela vôo. As duas colunas seguintes são provenientes da tabela aeroporto, que funciona como tabela do aeroporto de origem (início); as linhas são combinadas com base em identificação_aeroporto e identificacao_aeroporto_inicio das tabelas aeroporto e vôo, respectivamente. Em seguida vem uma coluna da tabela vôo . As duas últimas colunas são provenientes da tabela aeroporto, que dessa vez funciona como a tabela do aeroporto de destino (fim); as linhas são combinadas com base em identificação_aeroporto e identificacao_aeroporto_fim das tabelas aeroporto e vôo, respectivamente.

Ainda tem dúvidas sobre o uso de JOINs? Não se precupe, existem muitos outros artigos para você ler e aprender. Eu recomendo especialmente o artigo sobre Como Aprender Cláusulas JOIN em SQL. E se você deseja praticar, dê uma olhada no nosso artigo sobre Como Praticar Cláusulas JOIN em SQL.

Auto-Junção: Um caso especial da cláusula JOIN

Como vimos, a auto-junção é um caso especial importante de JOIN. Temos visto exemplos de várias aplicações da auto-junção, incluindo o processamento de uma hierarquia em uma tabela e o pareamento de linhas dentro de uma tabela. Podemos juntar a mesma tabela várias vezes, mas é importante dar a cada referência um pseudônimo que indique seu papel na consulta. Esses pseudônimos de tabela são usados para buscar colunas dentro da mesma tabela com base no papel que cada uma desempenha.

As cláusulas JOIN são uma parte vital do SQL e um recurso muito útil e muito utilizado para ligar diferentes tabelas. Elas estão em todos os lugares - não deixe de conferir nosso curso sobre Cláusulas JOIN em SQL para dominar esta ferramenta poderosa!