3rd Nov 2022 Tempo di lettura: 11 minuti Che cos'è un'operazione di self-join in SQL? Sette esempi Martyna Sławińska sql imparare sql join Indice Che cos'è un'operazione di self-join in SQL? Alias delle tabelle nelle operazioni di self-join Esempi Scenario 1: elaborazione di un organigramma in SQL Scenario 2: elencare le coppie all'interno di una tabella Scenario 3: self-join in combinazione con un'altra tabella Self-join: un caso speciale di operazione di unione Le operazioni di self-join sono un caso speciale molto diffuso di JOIN in SQL. Mentre la maggior parte delle operazioni JOIN collega due o più tabelle tra loro per presentare i loro dati insieme, una self-join collega una tabella a se stessa. Di solito questo avviene unendo una tabella a se stessa una sola volta all'interno di una query SQL, ma è possibile farlo più volte all'interno della stessa query. In genere, all'interno di ogni tabella di un database vengono memorizzate un tipo specifico di informazioni. Quindi, spesso in un database sono presenti centinaia di tabelle collegate tra loro. Ciò implica la necessità di effettuare operazioni di unione. È possibile unire tabelle diverse in base alle loro colonne comuni, utilizzando la parola chiave JOIN. È anche possibile unire una tabella a se stessa, con un'operazione nota come self-join. In questo articolo discuteremo cos'è una self-join, come funziona e quando è necessario usarla nelle query SQL. Per esercitarsi con le operazioni JOIN in SQL, comprese le self-join, vi consiglio il nostro corso interattivo JOIN in SQL. Contiene oltre 90 esercizi su diversi tipi di operazioni JOIN, comprese sezioni dedicate proprio alle self-join. Che cos'è un'operazione di self-join in SQL? Una self-join, come dice il nome, unisce una tabella a se stessa. Per realizzare un'operazione di self-join, la tabella deve contenere una colonna (chiamata X) che funge da chiave primaria e un'altra colonna (chiamata Y) che memorizza valori che possono essere abbinati ai valori della colonna X. I valori delle colonne X e Y non devono necessariamente essere gli stessi per ogni riga e il valore della colonna Y può anche essere null. Vediamo un esempio. Consideriamo la tabella dipendenti: idnome_completostipendioid_manager 1John Smith100003 2Jane Anderson120003 3Tom Lanon150004 4Anne Connor20000 5Jeremy York90001 Ogni dipendente ha il proprio id, che è la nostra "Colonna X". Per un dato dipendente (cioè una riga), la colonna id_manager contiene il codice id del suo manager; questa è la nostra "Colonna Y". Se rintracciamo le coppie dipendente-manager in questa tabella utilizzando queste colonne, otteniamo: Il manager del dipendente John Smith è il dipendente con id 3, cioè Tom Lanon. Il manager del dipendente Jane Anderson è il dipendente con id 3, cioè Tom Lanon. Il manager del dipendente Tom Lanon è il dipendente con id 4, cioè Anne Connor. La dipendente Anne Connor non ha un manager; il suo id_manager è null. Il manager del dipendente Jeremy York è il dipendente con id 1, cioè John Smith. Questo tipo di struttura delle tabelle è molto comune negli organigrammi. Ora, per mostrare il nome del manager di ciascun dipendente nella stessa riga, si può eseguire la seguente query: SELECT dipendente.id, dipendente.nome_completo, dipendente.id_manager, manager.nome_completo as nome_del_manager FROM dipendenti dipendente JOIN dipendenti manager ON dipendente.id_manager = manager.id che restituisce il seguente risultato: idnome_completoid_managernome_del_manager 1John Smith3Tom Lanon 2Jane Anderson3Tom Lanon 3Tom Lanon4Anne Connor 5Jeremy York1John Smith La query seleziona le colonne id, nome_completo e id_manager dalla tabella con alias dipendente. Seleziona anche la colonna nome_completo della tabella con alias manager e la designa come nome_del_manager. Di conseguenza, ogni dipendente a cui è assegnato un manager viene visualizzato insieme all'id e al nome di quest'ultimo. In questa query, la tabella dipendenti è unita a se stessa e ha due ruoli diversi: Ruolo 1: memorizza i dati dei dipendenti (alias dipendente). Ruolo 2: memorizza i dati dei manager (alias manager). In questo modo, si considerano essenzialmente le due copie della tabella dipendenti come se fossero due tabelle distinte, una per i dipendenti e una per i manager. Per saperne di più sul concetto di self-join, consultate il nostro articolo Guida illustrata alle operazioni di self-join in SQL. Alias delle tabelle nelle operazioni di self-join Quando si fa riferimento alla stessa tabella più di una volta in una query SQL, è necessario distinguere ogni riferimento dagli altri. Per questo motivo, è importante utilizzare alias per identificare in modo univoco ogni riferimento alla stessa tabella in una query SQL. Come buona prassi, gli alias devono indicare il ruolo della tabella per ogni specifico riferimento in una query. Gli alias sono segnati in rosso nella query seguente. Puoi notare la loro dichiarazione nelle clausole FROM e JOIN. SELECT dipendente.id, dipendente.nome_completo, dipendente.id_manager, manager.nome_completo as nome_del_manager FROM dipendenti dipendente JOIN dipendenti manager ON dipendente.id_manager = manager.id La parola chiave JOIN collega due tabelle ed è solitamente seguita da una clausola ON o USING che specifica le colonne comuni utilizzate per collegare le due tabelle. In questo caso, vediamo che i due riferimenti alla tabella dipendenti sono collegati utilizzando come condizione la corrispondenza tra l'id_manager di un dipendente con l'id dipendente del manager. Esempi Vediamo alcuni scenari comuni in cui viene utilizzata l'operazione di self-join. Scenario 1: elaborazione di un organigramma in SQL Le operazioni di self-join vengono usate comunemente nell'elaborazione degli organigrammi. Come abbiamo visto in precedenza, all'interno di un organigramma viene assegnata una riga di una tabella a un'altra riga della stessa tabella. Potete pensare che questo tipo di dati vengono espressi con righe padre e righe figlio. Torniamo all'esempio con i dipendenti e i rispettivi manager. Ecco di nuovo la tabella dipendenti: idnome_completostipendioid_manager 1John Smith100003 2Jane Anderson120003 3Tom Lanon150004 4Anne Connor20000 5Jeremy York90001 E il codice per elencare ogni dipendente che ha un manager con il nome del proprio manager: SELECT dipendente.id, dipendente.nome_completo, dipendente.id_manager, manager.nome_completo as nome_del_manager FROM dipendenti dipendente JOIN dipendenti manager ON dipendente.id_manager = manager.id Ecco il risultato dell'esecuzione del codice: idnome_completoid_managernome_del_manager 1John Smith3Tom Lanon 2Jane Anderson3Tom Lanon 3Tom Lanon4Anne Connor 5Jeremy York1John Smith Questa query utilizza l'operatore JOIN standard, noto anche come INNER JOIN. Per saperne di più su INNER JOIN, consultate il nostro articolo Guida illustrata a INNER JOIN in SQL. Se si desidera elencare tutti i dipendenti, indipendentemente dal fatto che abbiano o meno un manager, è possibile utilizzare l'operatore LEFT OUTER JOIN. Potete vederne un esempio nella query di seguito: SELECT dipendente.id, dipendente.nome_completo, dipendente.id_manager, manager.nome_completo as nome_del_manager FROM dipendenti dipendente LEFT OUTER JOIN dipendenti manager ON dipendente.id_manager = manager.id Quando si esegue questa query, si ottiene il seguente risultato: idnome_completoid_managernome_del_manager 1John Smith3Tom Lanon 2Jane Anderson3Tom Lanon 3Tom Lanon4Anne Connor 4Anne Connor 5Jeremy York1John Smith La differenza tra JOIN e LEFT OUTER JOIN diventa evidente se confrontiamo il risultato di quest'ultima query SQL con quella precedente. Nel risultato della query (INNER) JOIN sono inclusi solo i dipendenti con manager. Al contrario, la query LEFT OUTER JOIN restituisce tutti i dipendenti, con o senza manager. Per saperne di più sugli operatori OUTER JOIN, leggete il nostro articolo Guida illustrata ad OUTER JOIN in SQL. Un altro esempio di gerarchia è la relazione tra genitori e figli. Si consideri la tabella umani mostrata qui: idnomeetaid_genitore 1Jonathan53 2Alexandra73 3Barbara30 Nella query che segue, i figli vengono assegnati ai rispettivi genitori unendo la tabella umani a se stessa: SELECT figli.id as id_figlio, figli.nome as nome_del_figlio, figli.eta as eta_del_figlio, figli.id_genitore, genitori.nome as nome_del_genitore, genitori.eta as eta_del_genitore FROM umani figli INNER JOIN umani genitori ON figli.id_genitore = genitori.id Ecco il risultato di questa query: id_figlionome_del_figlioeta_del_figlioid_genitorenome_del_genitoreeta_del_genitore 1Jonathan53Barbara30 2Alexandra73Barbara30 Il risultato della query include solo i figli che hanno i genitori. Come nell'esempio dell'organigramma dipendenti-dirigenti, è possibile utilizzare LEFT OUTER JOIN per includere tutte le righe della tabella con alias figli. Ecco un altro esempio di set di dati gerarchico. Si consideri la tabella categorie mostrata di seguito: idquantitacategoriaid_categoria_padre 160Cibo 250Frutta1 340Mela2 420Granny Smith3 5100Latte1 660Latte di soia5 740Latte vaccino5 830Latte intero7 910Latte scremato7 Quando è possibile, assegniamo una categoria padre a ogni categoria. Ecco una query per farlo: SELECT cat.id, cat.quantita, cat.categoria, cat.id_categoria_padre, cat_padre.categoria as categoria_padre FROM categorie cat JOIN categorie cat_padre ON cat.id_categoria_padre = cat_padre.id Ed ecco il risultato: idquantitacategoriaid_categoria_padrecategoria_padre 250Frutta1Cibo 340Mela2Frutta 420Granny Smith3Mela 5100Latte1Cibo 660Latte di soia5Latte 740Latte vaccino5Latte 830Latte intero7Latte vaccino 910Latte scremato7Latte vaccino Le prime quattro colonne del risultato sopra riportato provengono dal riferimento alla tabella con alias cat. L'ultima colonna proviene dalla tabella con alias cat_padre e contiene il nome della categoria padre a cui corrisponde il rispettivo id. La tabella categoria riveste due ruoli diversi, come indicato dai due riferimenti distinti. La colonna id_categoria_padre della tabella con alias cat è abbinata a l'id della tabella con alias cat_padre. La clausola ON specifica che id_categoria_padre di cat deve essere uguale all'id di cat_padre per collegare le righe corrispondenti. Scenario 2: elencare le coppie all'interno di una tabella È possibile utilizzare un'operazione di self-join per generare coppie di righe in base alla condizione della clausola ON. Cominciamo con un semplice esempio che genera tutte le coppie possibili tra i colleghi. Consideriamo la seguente tabella, colleghi: idnome_completoeta 1Bart Thompson43 2Catherine Anderson44 3John Burkin35 4Nicole McGregor29 Supponiamo di dover generare tutte le coppie possibili tra i colleghi, in modo che tutti abbiano la possibilità di parlare con tutti gli altri alla serata di presentazione dell'azienda. Ecco il codice SQL: SELECT compagno1.nome_completo as nome_completo_compagno1, compagno1.eta as eta_compagno1, compagno2.nome_completo as nome_completo_compagno2, compagno2.eta as eta_compagno2 FROM colleghi compagno1 CROSS JOIN colleghi compagno2 ON compagno1.nome_completo <> compagno2.nome_completo Ed ecco il risultato: nome_completo_compagno1eta_compagno1nome_completo_compagno2eta_compagno2 Catherine Anderson44Bart Thompson43 John Burkin35Bart Thompson43 Nicole McGregor29Bart Thompson43 Bart Thompson43Catherine Anderson44 John Burkin35Catherine Anderson44 Nicole McGregor29Catherine Anderson44 Bart Thompson43John Burkin35 Catherine Anderson44John Burkin35 Nicole McGregor29John Burkin35 Bart Thompson43Nicole McGregor29 Catherine Anderson44Nicole McGregor29 John Burkin35Nicole McGregor29 Il risultato fa corrispondere ogni singola persona della tabella con ogni altra. Poiché non vogliamo che nessuno sia abbinato a se stesso, abbiamo inserito la condizione della clausola ON compagno1.nome_completo <> compagno2.nome_completo . Ciò significa che ogni persona sarà abbinata ad altri tre colleghi, perché in questo evento ci sono quattro colleghi. Vediamo ora un esempio un po' più complicato. Consideriamo la tabella umani riportata di seguito. Vogliamo abbinare tutti gli antenati a ogni persona, laddove i dati lo consentano, con la condizione che gli antenati abbiano un valore id più alto o uguale rispetto all'id_genitore della persona. Di seguito è riportata la tabella umani utilizzata in questo esempio. idnomeetaid_genitore 1Jonathan53 2Alexandra73 3Barbara304 4Tom506 5George556 6Amy807 7Josephine9935 Troviamo tutte le coppie discendente-antenato nella tabella precedente. Ecco il codice SQL: SELECT discendente.id, discendente.nome, discendente.eta, discendente.id_genitore, antenato.id as id_antenato, antenato.nome as nome_antenato, antenato.eta as eta_antenato FROM umani discendente LEFT JOIN umani antenato ON discendente.id_genitore <= antenato.id E il risultato: idnomeetaid_genitoreid_antenatonome_antenatoeta_antenato 1Jonathan533Barbara30 1Jonathan534Tom50 1Jonathan535George55 1Jonathan536Amy80 1Jonathan537Josephine99 2Alexandra733Barbara30 2Alexandra734Tom50 2Alexandra735George55 2Alexandra736Amy80 2Alexandra737Josephine99 3Barbara3044Tom50 3Barbara3045George55 3Barbara3046Amy80 3Barbara3047Josephine99 4Tom5066Amy80 4Tom5067Josephine99 5George5566Amy80 5George5567Josephine99 6Amy8077Josephine99 7Josephine99 Specificando la condizione della clausola ON discendente.id_genitore <= antenato.id , troviamo tutti gli antenati presenti di ogni persona nella tabella; in caso contrario, la query restituisce null per le informazioni sugli antenati. Le prime quattro colonne sono prese dalla tabella con alias discendente che contiene le informazioni sulla persona per la quale si cercano gli antenati. Le ultime tre colonne sono tratte dalla tabella con alias antenato e contengono dettagli su ciascun antenato. Scenario 3: self-join in combinazione con un'altra tabella In SQL è possibile utilizzare un'operazione di self-join in combinazione con una o più tabelle diverse. Anche se non si tratta di una self-join pura, si tratta di un impiego molto comune nella pratica. Un esempio reale è quello delle informazioni sui voli negli aeroporti, con un'enorme quantità di dati ogni ora. Supponiamo di voler cercare il numero di identificazione di un volo insieme ai dettagli sugli aeroporti di partenza e di destinazione. Consideriamo le seguenti tabelle: Tabella aeroporti: id_aeroportopaesecitta 1USANew York 2CanadaToronto 3GermaniaFrancoforte 4FranciaParigi 5ItaliaRoma Tabella voli: id_voloid_aereotimestamp_di_partenzatimestamp_di_arrivoid_aeroporto_di_partenzaid_aeroporto_di_arrivo 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 Qui notate che la colonna id_aeroporto della tabella aeroporti è la chiave esterna delle colonne id_aeroporto_di_partenza e id_aeroporto_di_arrivo della tabella voli. Uniremo la tabella aeroporti alla tabella voli due volte distinte, come segue: Nella prima operazione JOIN, aeroporti prende il ruolo della tabella con gli aeroporti di partenza. Nella seconda operazione JOIN, aeroporti prende il ruolo della tabella con gli aeroporti di arrivo. La query ha questo aspetto: SELECT volo.id_volo, volo.id_aereo, volo.id_aeroporto_di_partenza, aeroportopartenza.paese as paese_aeroporto_di_partenza, aeroportopartenza.citta as citta_aeroporto_di_partenza, volo.id_aeroporto_di_arrivo, aeroportoarrivo.paese as paese_aeroporto_di_arrivo, aeroportoarrivo.citta as citta_aeroporto_di_arrivo FROM voli volo JOIN aeroporti aeroportopartenza ON volo.id_aeroporto_di_partenza = aeroportopartenza.id_aeroporto JOIN aeroporti aeroportoarrivo ON flight.id_aeroporto_di_arrivo = aeroportoarrivo.id_aeroporto E il risultato della query si presenta così: id_voloid_aereoid_aeroporto_di_partenzapaese_aeroporto_di_partenzacitta_aeroporto_di_partenzaid_aeroporto_di_arrivopaese_aeroporto_di_arrivocitta_aeroporto_di_arrivo 11116541USANew York2CanadaToronto 25558773GermaniaFrancoforte4FranciaParigi 32225361USANew York5ItaliaRoma 41117455ItaliaRoma4FranciaParigi 57775244FranciaParigi2CanadaToronto 68885212CanadaToronto1USANew York 74449373GermaniaFrancoforte1USANew York Analizziamo il risultato. Le prime tre colonne provengono da un semplice SELECT della tabella voli. Le due colonne successive provengono da aeroporti nel ruolo di tabella degli aeroporti di partenza; le righe sono abbinate in base a id_aeroporto e id_aeroporto_di_partenza delle tabelle aeroporti e voli rispettivamente. Segue una colonna della tabella voli. Le ultime due colonne provengono da aeroporti, questa volta nel ruolo di tabella degli aeroporti di arrivo; le righe sono abbinate in base a id_aeroporto e id_aeroporto_di_arrivo rispettivamente dalle tabelle aeroporti e voli. Siete ancora un po' confusi su tutte le operazioni JOIN? Esistono molti altri articoli che potete consultare per trovare aiuto. Vi consiglio in particolare l'articolo su Come imparare gli operatori JOIN in SQL. E se avete bisogno di iniziare a esercitarvi, visitate il nostro articolo su Come esercitarsi con gli operatori JOIN in SQL. Self-join: un caso speciale di operazione di unione Come abbiamo visto, l'operazione di self-join è un importante caso speciale di unione. Abbiamo visto esempi di varie applicazioni di self-join, tra cui l'elaborazione di una gerarchia in una tabella e l'accoppiamento delle righe in una tabella. Possiamo unire la stessa tabella più volte, ma è importante dare a ogni riferimento un alias che ne indichi il ruolo. Questi alias delle tabelle vengono utilizzati per recuperare le colonne da una singola tabella, in base al ruolo che interpreta nel momento in cui viene usata come riferimento. Le operazioni di unione sono una parte fondamentale di SQL, una funzione molto utile e frequentemente utilizzata per combinare tabelle diverse. Si trovano dappertutto: date un'occhiata al nostro corso su JOIN in SQL per imparare a usare questo potente strumento! Tags: sql imparare sql join