Nelle precedenti lezioni abbiamo visto come eseguire le principali operazione su un database relazionale tramite SQL, descrivendo il funzionamento e la sintassi delle istruzioni INSERT
, SELECT
, UPDATE
e DELETE
. Alcune situazioni più complesse, inoltre, possono essere gestite sfruttando le cosiddette subquery, cioè inserendo l'istruzione SELECT
all'interno di query che ne contengono già una, o che contengono altre istruzioni come le precedenti.
Nella lezione sull'istruzione UPDATE, abbiamo già visto un esempio di subquery, che riportiamo qui di seguito:
UPDATE Book SET note= 'editore: ' + (SELECT name FROM Publisher WHERE Publisher.ID = Book.publisher)
In questo caso, all'interno di una query di modifica dei dati, abbiamo avuto bisogno di selezionare una serie di record sfruttando un'intera query di selezione.
Di seguito approfondiremo più nel dettaglio come e quando ha senso l'uso di una subquery, con alcuni esempi pratici di utilizzo.
Operazioni su subquery
Per capire in che modo utilizzare una subquery, è necessario innanzitutto soffermarsi sui possibili valori di ritorno di una query di selezione (ovvero di una query che utilizza l'istruzione SELECT
). Lo faremo sfruttando alcuni esempi, ognuno dei quali utilizza un diverso tipo di subquery, e che ci porterà a discutere alcuni costrutti interessanti del linguaggio SQL.
La parola chiave IN
Iniziamo con la query seguente:
SELECT *
FROM Clienti
WHERE id IN (SELECT idCliente
FROM Incassi
WHERE importo > 1000)
La subquery interna ritorna un insieme di id di clienti, e più precisamente quelli per i quali il campo importo supera il valore 1000. Se osserviamo ora l'intera query, noteremo la parola chiave IN
: dal momento che è inserito all'interno di una clausola WHERE
, questo costrutto ci permette di selezionare tutte e sole le righe della tabella Clienti tali che il campo id sia presente tra i risultati della subquery. Utilizzando NOT IN
otterremmo, ovviamente, l'esatto contrario: selezioneremmo tutti quei clienti il cui id NON è presente tra i risultati della subquery.
Le parole chiave SOME, ANY ed ALL
Altre possibilità offerte da SQL sono rappresentate dalle parole chiave SOME
, ANY
ed ALL
. Immaginiamo di volere selezionare una riga della tabella Incassi solo se la colonna importo è maggiore di almeno uno dei valori ritornati da una subquery. Possiamo implementare questa logica con la parola chiave SOME
(nonchè con la parola chiave ANY
, che è in tutto e per tutto equivalente):
SELECT *
FROM Incassi
WHERE importo > SOME (SELECT costo
FROM Commesse
WHERE tipologia = 'commessaSemplice')
Modificare il funzionamento di un operatore di confronto come >
ci permette di verificare se un valore di un campo (in questo caso importo) è maggiore di almeno uno degli elementi di un insieme di valori (ovvero il risultato della subquery).
Modifichiamo ora la precedente query utilizzando la parola chiave ALL
al posto di SOME
:
SELECT *
FROM Incassi
WHERE importo > ALL (SELECT costo
FROM Commesse
WHERE tipologia = 'commessaSemplice')
Con ALL
ci assicureremo che il valore di importo sia maggiore di tutti i valori contenuti nell'insieme dei risultati ritornato dalla subquery.
EXISTS e NOT EXISTS
In stretta correlazione con gli operatori IN
e NOT IN
ci sono anche EXISTS
e NOT EXISTS
: essi permettono, infatti, di verificare se una subquery ritorna una o più righe, ovvero se essa produce un risultato vuoto. La parola chiave EXISTS
, in particolare, ritornerà un valore booleano pari a TRUE
se e solo se la subquery successiva seleziona almeno una riga. Vediamo un esempio:
SELECT DISTINCT idCitta
FROM Citta
WHERE EXISTS (SELECT *
FROM Citta_SquadreCalcio
WHERE Citta.idCitta = Citta_SquadreCalcio.idCitta)
La query, come si evince, ritorna gli id delle città per le quali esiste almeno una squadra di calcio all'interno della tabella Citta_SquadreCalcio.
Mentre l'uso di EXISTS
è facilmente comprensibile, ciò che può risultare meno intuitivo è il modo in cui la subquery è correlata con la query principale: all'interno della clausola WHERE
, infatti, abbiamo utilizzato il campo idCitta della tabella Citta. Ciò implica che la subquery dovrà essere eseguita con un parametro diverso per ogni riga estratta dalla query esterna. Il risvolto pratico è un significato degrado delle prestazioni: in questi casi è sempre meglio preferire soluzioni alternative, considerato anche che spesso le subquery correlate possono essere trasformate in operazioni di JOIN
o simili. Lo stesso risultato della query precedente, ad esempio, si poteva ottenere come segue:
SELECT DISTINCT idCitta
FROM Citta, Citta_SquadreCalcio
WHERE Citta.idCitta = Citta_SquadreCalcio.idCitta
Subquery e funzioni di aggregazione
Un altro possibile uso delle subquery è esemplificabile come segue:
SELECT *
FROM Studenti
WHERE voto > (SELECT AVG(voto)
FROM Studenti)
In questo caso, e a differenza di quanto visto finora, la subquery non ritorna una serie di risultati, bensì esattamente un valore (in questo caso la media dei voti di un insieme di studenti). È quindi chiaro in che modo è possibile utilizzare una subquery che sfrutta una funzione di aggregazione (in questo caso AVG
) all'interno di una clausola WHERE
.
Subquery all'interno di FROM
Per concludere questa lezione, osserviamo che tutte le subquery viste finora sono state utilizzate, con gli opportuni operatori, all'interno di una clausola WHERE
, per lo più in qualità di operandi su operazioni di confronto.
È altresì possibile prevedere l'uso delle subquery all'interno di una clausola FROM
, indipendentemente dal fatto che essa faccia parte di una query di selezione (SELECT
) piuttosto che di eliminazione (DELETE
), inserimento (INSERT
) o aggiornamento (UPDATE
). Consideriamo l'esempio che segue:
UPDATE Tabella1
SET colonna1 = T.colonna1
FROM (SELECT *
FROM Tabella2) AS SubQuery
INNER JOIN Tabella1 ON Tabella1.colonnaX = SubQuery.colonnaY
In questa operazione di UPDATE
abbiamo effettuato un INNER JOIN
tra la Tabella1 ed il risultato di una subquery: il contenuto ritornato dalla subquery è utilizzato come una vera e propria tabella, all'interno della clausola FROM
. L'unica accortezza che diventa necessaria in questi casi è la necessità di utilizzare la parola chiave AS
per dare un nome temporaneo al risultato della subquery (e poterne quindi richiamare i campi all'interno delle clausole WHERE
o per effettuare un JOIN
).