8th Aug 2022 11 minutos de leitura O Que é um Self Join em SQL? Sete Exemplos para Explicar Martyna Sławińska sql aprender sql joins Índice O que é um Self Join em SQL? Tabela com Pseudônimos em Self Join Exemplos Cenário 1: Processamento de uma Hierarquia em SQL Cenário 2: Listando pares dentro de uma tabela Cenário 3: Fazendo um Self Join com Outra Tabela Auto-Junção: Um caso especial da cláusula JOIN 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! Tags: sql aprender sql joins