Join SQL

Il linguaggio SQL è uno degli strumenti più utilizzato nel mondo dell'informatica. Esso è presente quasi ovunque: applicazioni locali, sistemi bancari, siti web, e-commerce, forum, blog ed altro. La conoscenza dell SQL è una competenza fondamentale, trasversale a molte tecnologie, che tutti i programmatori dovrebbero conoscere. Per nostra fortuna il linguaggio SQL è semplice da imparare, e può essere riassunto (a grandi linee) in tre tipologie di istruzioni: SELECT (cercare i dati), INSERT (modificare i dati) e UPDATE (modificare i dati).

Esistono ovviamente costrutti più complessi, che vengono utilizzati raramente, per cui possiamo permetterci di studiarli all'occorenza. Oltre a questi il linguaggio SQL prevede però un'istruzione d'uso frequente che non è sempre chiara ai principianti: l'istruzione di JOIN tra tabelle.

Questo breve tutorial ambisce ad introdurre (o chiarire) il funzionamento del Join in modo semplice ed efficace, mostrando alcuni esempi concreti. In particolare faremo riferimento sia alla sintassi standard dell'istruzione Join, sia alla sintassi Oracle: in questo modo cercheremo di chiarire sia i dubbi semantici (significato dell'istruzione) che quelli sintattici (differenze tra i dialetti SQL).

Contenuti


Scenario di lavoro

Consideriamo la tabella:

Tabella TEST_JOIN_FAMILY
Esempio di tabella TEST_JOIN_FAMILY

dove sono memorizzati i dati di alcune famiglie, ovvero alcune informazioni comuni (di solito) alle persone residenti nello stessa abitazione o appartamento.

Chiameremo questa tabella TEST_JOIN_FAMILY, o semplicemente "tabella delle famiglie".

Le informazioni specifiche per le singole persone sono invece memorizzate nella tabella TEST_JOIN_PEOPLE:

Tabella TEST_JOIN_PEOPLE
Esempio di tabella TEST_JOIN_PEOPLE

Notiamo che ad ogni persona è associato un FAMILY_ID, ovvero un numero che indica la sua famiglia di appartenza. Tale codice numerico è correlato ai dati della famiglia definiti nella tabella precedente (la TEST_JOIN_FAMILY).

Notiamo anche che la colonna SURNAME è presente in entrambe le tabelle. Questo non è un errore, perché il cognome nella TEST_JOIN_FAMILY rappresenta il cognome della famiglia, mentre quello nella TEST_JOIN_PEOPLE rappresenta quello della singola persona, che in generale potrebbe essere diverso (esempio: coppia non sposata).

Le due tabelle sono correlate in modo relazionale, proprio in virtù della colonna FAMILY_ID, che funge da "codice di correlazione" o "link" tra le due tipologie di dati. Di seguito vedremo come utilizzare l'istruzione Join per collegare tra loro i record delle due tabelle.

Download

Tutti gli esempi presentati in questo tutorial sono facilmente replicabili su un qualsiasi database Oracle, semplicemente scaricando gli script di creazione delle tabelle, assieme alle istruzioni SQL di creazione e manipolazione dei dati. In questo modo dovrebbe essere più facile comprendere il funzionamento del JOIN, provandolo in prima persona.

Outer Join

Iniziamo col vedere il caso più comune di Join tra due tabelle: vogliamo visualizzare tutte le informazioni disponibili per ogni persona, recuperando i dati da entrambe le tabelle. In altre parole vogliamo "arricchire" i dati della tabella TEST_JOIN_PEOPLE coi dati memorizzati nella tabella TEST_JOIN_FAMILY. L'istruzione che permette di ottenere questo risultato è il "join classico":

SELECT * FROM TEST_JOIN_FAMILY family, TEST_JOIN_PEOPLE people
WHERE family.id = people.family_id 
ORDER BY family.ID;

In questo caso si parla di OUTER JOIN, perché l'istruzione indica l'aggregazione (join) di una tabella con una sorgente dati "esterna" (outer). Il risultato sarà qualcosa del genere:

OUTER JOIN
Esempio di OUTER JOIN

Dalla figura qui sopra notiamo che l'istruzione ritorna solamente i record aventi lo stesso FAMILY_ID in entrambe le tabelle. Siccome nella tabella TEST_JOIN_FAMILY i valori della colonna FAMILY_ID sono compresi tra 1 e 5, gli elementi della TEST_JOIN_PEOPLE aventi FAMILY_ID fuori da questo intervallo (ad esempio 10 o 9) non vengono selezionati dall'istruzione Join.

Questo è il funzionamento normale dell'istruzione Join. Se invece vogliamo "alleggerire" questo vincolo, ovvero vogliamo visualizzare anche i record che non soddisfano la condizione "avere lo stesso FAMILY_ID in entrambe le tabelle", possiamo utilizzare un LEFT OUTER JOIN oppure un RIGHT OUTER JOIN, come mostrato più sotto.

Left Outer Join

Il Join "a sinistra" ritorna tutti i record restituiti dal Join normale (i.e. un Outer Join), più tutti i record presenti nella tabella scritta "a sinistra" dell'istruzione Join. Consideriamo ad esempio:

SELECT * FROM TEST_JOIN_FAMILY family LEFT OUTER JOIN TEST_JOIN_PEOPLE people
ON family.id = people.family_id
ORDER BY family.id;

che restituisce

LEFT OUTER JOIN

Esempio di LEFT OUTER JOIN

Vedi anche Right Outer Join

In questo caso, siccome l'istruzione indica LEFT OUTER JOIN, vengono riportati tutti i record presenti nella tabella "a sinistra" (i.e la TEST_JOIN_FAMILY), anche se essi non soddisfano la condizione del Join. In altre parole vengono riportati i record di tutte le famiglie, anche quelle che non sono collegate a persone memorizzate nella TEST_JOIN_PEOPLE.

Sintassi Oracle

La vecchia sintassi Oracle, tutt'ora assai usata, permette di scrivere un LEFT OUTER JOIN nel seguente modo:

Select * From TEST_JOIN_FAMILY Family, TEST_JOIN_PEOPLE People
WHERE family.id = people.family_id (+) 
ORDER BY family.id ;

in questo caso il simbolo "(+)" indica la tabella "aggiuntiva", presente come "surplus", cioè contenente i "record opzionali". Nel caso qui sopra il simbolo "(+)" si trova a destra del simbolo "=", per cui i record opzionali sono quelli della tabella TEST_JOIN_PEOPLE, che infatti è posizionata a destra della condizione di Join (cioè la family.id = people.family_id). Da ciò segue che i record obbligatori (i.e. sempre presenti) sono memorizzati nella tabella "a sinistra", ovvero la TEST_JOIN_FAMILY.

In altre parole la sintassi qui sopra (quella col simbolo "(+)") è un modo alternativo di definire un LEFT OUTER JOIN, perché difatto i record "sempre presenti" sono quelli relativi alla tabella scritta "a sinistra", come nel caso precedente.

Suggerimento: un modo semplice per ricordare la sintassi Oracle consiste nel "ribaltare mentalmente" la posizione del simbolo "(+)": se il simbolo si trova a destra della condizione del Join, si tratta di un LEFT Join. Viceversa, se il simbolo si trova a sinistra, allora si tratta di un RIGHT Join.

Right Outer Join

Il Join "a destra" ritorna tutti i record restituiti dal Join normale (i.e. un Outer Join), più tutti i record presenti nella tabella scritta "a destra" dell'istruzione Join. Consideriamo ad esempio:

SELECT * FROM TEST_JOIN_FAMILY family RIGHT OUTER JOIN TEST_JOIN_PEOPLE people
ON family.id = people.family_id 
ORDER BY family.id ;

che restituisce

RIGHT OUTER JOIN

Esempio di RIGHT OUTER JOIN

Vedi anche Left Outer Join

Rispetto al caso precedente, adesso otteniamo tutti record memorizzati nella tabella di destra, anche se non soddisfano la condizione del Join (cioè la family.id = people.family_id). Infatti nella figura qui sopra compaiono anche i record relativi alle persone non associate ad alcuna famiglia, perché tali record appartengono alla tabella TEST_JOIN_PEOPLE (scritta "a destra").

Anche in questo caso possiamo ottenere lo stesso risultato usando la sintassi Oracle, ovvero

SELECT * FROM TEST_JOIN_FAMILY Family, TEST_JOIN_PEOPLE People
WHERE family.id (+) = people.family_id 
ORDER BY family.id ;

Conclusioni

Riassumendo, dal punta di vista pratico possiamo così riassumere i concetti essenziali relativi all'istruzione Join:

  • LEFT/RIGHT OUTER JOIN: l'etichetta "LEFT" o "RIGHT" indica quali tra i record, specificati dalla tabella scritta "a sinistra" o "a destra" della scritta JOIN, devono essere sempre presenti. Quindi LEFT significa "tutti i record della tabella di sinistra", mentre RIGHT significa "tutti i record della tabella di destra".
  • Sintassi Oracle: il simbolo "(+)" va semplicemente letto al "contrario" rispetto alla propria posizione. Se il simbolo "(+)" è a sinistra, abbiamo un LEFT Join. Se si trova a destra, abbiamo un RIGHT Join. In questo caso i riferimenti "sinistra" e "destra" fanno riferimento alla posizione rispetto al simbolo "=" (che sostituisce l'etichetta JOIN).
  • Criterio: nella sintassi standard il "punto di riferimento" rispetto al quale definire cosa sta "a sinistra" e cosa sta "a destra" coincide con l'etichetta LEFT/RIGHT OUTER JOIN, perciò é del tutto indifferente la posizione delle colonne del Join rispetto al simbolo "=". Al contrario, nella vecchia sintassi Oracle, il "punto di riferimento" è proprio il simbolo "=", per cui occore fare attenzione a cosa scriviamo a destra oppure a sinistra dell'uguaglianza.

Note

Per verificare se abbiamo compreso il funzionamento del Join, suggeriamo il seguente esercizio: si prende una delle istruzione qui sopra (ad esempio quelle del Left Outer Join) e si prova ad ottenere i risultati del caso opposto (cioè del Right Outer Join) semplicemente invertendo la posizione di opportuni elementi.

Ad esempio, se invertiamo la posizione dei nomi delle tabelle nell'istruzione SQL relativa al Left Outer Join, cioè se scriviamo

SELECT * FROM TEST_JOIN_PEOPLE people LEFT OUTER JOIN TEST_JOIN_FAMILY family
ON family.id = people.family_id
ORDER BY family.id ;

Troveremo lo stesso risultato che prima abbiamo ottenuto con un Right Outer Join.

Ciò è corretto, perché trattandosi di un Left Outer Join significa che la tabella scritta a sinistra (la TEST_JOIN_PEOPLE) è quella i cui record devono essere sempre presenti. La query qui sopra seleziona quindi tutti i record relativi alle persone, anche se non corrispondono ad una riga nelle tabella delle famiglie, esattamente come prima succedeva nel caso del Right Outer Join.

Se abbiamo già scritto la nostra query in un certo modo, usando ad esempio l'espressione LEFT OUTER JOIN, e vogliamo invertire il criterio di selezione, possiamo indifferentemente invertire la posizione delle tabelle oppure sostituire l'etichetta LEFT con l'etichetta RIGHT. Dal punto di vista del risultato finale le due scelte sono equivalenti, per cui possiamo scegliere la strada che più ci piace. A seconda della situazione, del contesto e della metodologia di sviluppo una scelta potrebbe risultare preferibile per motivi "estetici" o "paradigmatici". Ad esempio, se la nostra query risultasse più leggibile introducendo prima la tabella A, e poi la tabella B, allora il modo migliore di ribaltare il risultato è quello di sostituire l'etichetta LEFT con l'etichetta RIGHT (o viceversa). Al contrario, se per qualche motivo ci venisse chiesto di usare solamente i Right Join, dimenticandoci dei Left Join, allora possiamo giocare sulla posizione dei nomi delle tabelle per ottenere il risultato richiesto.

La flessibilità (o ridondanza) della notazione può creare un po' di confusione all'inizio, ma ciò è caratteristico di qualsiasi sistema che permette una certa flessibilità. Con un po' di pratica possiamo imparare a padroneggiare le diverse notazioni, oppure scegliere quella che più ci piace, a seconda delle esigenze.


Download

Scarica il pacchetto contenente il tutorial

Join Example Package


Voci correlate: Adriani Oracle Project