Congratulazioni, siamo arrivati al punto in cui le cose diventano davvero interessanti! Oggi vediamo come combinare diversi tipi di subquery per risolvere problemi complessi. EXISTS, IN, HAVING — questo trio ti farà sentire un vero mago dei database. Prenderemo dati da una tabella, li filtreremo usando dati da un'altra, li raggrupperemo e poi filtreremo i gruppi. E come bonus — vediamo anche qualche trucco per rendere le query più efficienti.
Iniziamo con la definizione di un problema generale che risolveremo passo dopo passo durante la lezione.
Definizione del problema
Supponiamo di avere un database universitario con tre tabelle:
Tabella students
| id | name | group_id |
|---|---|---|
| 1 | Otto | 101 |
| 2 | Maria | 101 |
| 3 | Alex | 102 |
| 4 | Anna | 103 |
Tabella courses
| id | name |
|---|---|
| 1 | Matematica |
| 2 | Programmazione |
| 3 | Filosofia |
Tabella enrollments
| student_id | course_id | grade |
|---|---|---|
| 1 | 1 | 90 |
| 1 | 2 | NULL |
| 2 | 1 | 85 |
| 3 | 3 | 70 |
Dobbiamo selezionare tutti gli studenti che:
- Sono iscritti almeno a un corso
EXISTS. - Non hanno un voto almeno in uno dei corsi a cui sono iscritti
IN. - Appartengono a gruppi in cui la media dei voti è superiore a 80
HAVING.
Soluzione usando EXISTS e IN
Passo 1: Controllo degli studenti iscritti (EXISTS). Partiamo dalla condizione più semplice. Dobbiamo capire chi tra gli studenti è iscritto almeno a un corso. Per questo usiamo EXISTS.
SELECT name
FROM students s
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE e.student_id = s.id
);
- La query esterna seleziona i nomi dalla tabella
students. - Nella subquery controlliamo se ci sono record nella tabella
enrollmentsche corrispondono allo studente della query esterna (WHERE e.student_id = s.id). SELECT 1serve solo per dire che ci interessa la presenza di record, non il loro contenuto.
Risultato:
| name |
|---|
| Otto |
| Maria |
| Alex |
Ora sappiamo chi tra gli studenti è iscritto ai corsi. Ma vogliamo di più. Vogliamo filtrarli per assenza di voto.
Passo 2: Controllo dell'assenza di voto (IN + NULL). Ora aggiungiamo il filtro: ci servono solo gli studenti che hanno almeno un corso senza voto. Qui ci aiutano IN e la conoscenza di come funziona NULL.
SELECT name
FROM students s
WHERE id IN (
SELECT e.student_id
FROM enrollments e
WHERE e.grade IS NULL
);
- Nella query esterna selezioniamo i nomi degli studenti.
- La subquery crea una lista di
student_iddalla tabellaenrollmentsdovegrade IS NULL.
Risultato:
| name |
|---|
| Otto |
Quindi, Otto è l'unico studente che ha un corso senza voto. Che dramma! Ma non abbiamo ancora finito: dobbiamo considerare solo i gruppi con media superiore a 80.
Soluzione usando HAVING
Passo 3: Raggruppamento e filtro con HAVING.
Ora è il momento di mettere tutto insieme. Dobbiamo:
- Calcolare la media dei voti per ogni gruppo.
- Filtrare i gruppi con media superiore a 80.
- Mostrare gli studenti di questi gruppi, considerando le condizioni precedenti.
SELECT name
FROM students s
WHERE s.group_id IN (
SELECT group_id
FROM students
JOIN enrollments ON students.id = enrollments.student_id
WHERE grade IS NOT NULL
GROUP BY group_id
HAVING AVG(grade) > 80
)
AND id IN (
SELECT e.student_id
FROM enrollments e
WHERE e.grade IS NULL
);
- La query esterna seleziona i nomi degli studenti che rispettano tutte le condizioni.
- La prima subquery in
WHERErestituisce la lista digroup_idper i gruppi con media superiore a 80.- Facciamo un join tra
studentseenrollmentsper ottenere i voti. - Filtriamo solo i record dove
grade IS NOT NULL. - Raggruppiamo per
group_id. - Usiamo
HAVINGper filtrare i gruppi.
- Facciamo un join tra
- La seconda subquery in
WHEREcontrolla che lo studente abbia almeno un corso senza voto. - Le due condizioni sono unite con
AND.
Risultato:
| name |
|---|
| Otto |
Quindi abbiamo scoperto che Otto non è solo l'unico studente senza voto, ma appartiene anche a un gruppo che va alla grande.
Confronto tra approcci: EXISTS vs IN
EXISTS funziona meglio quando vuoi controllare velocemente la presenza di record. È efficiente perché si ferma appena trova il primo record. Questo è importante soprattutto con tabelle grandi.
Allo stesso tempo IN è utile quando ti interessa il contenuto dei dati. Per esempio, se vuoi una lista di identificatori (id) per filtri successivi. Ma occhio: IN può diventare lento se la subquery restituisce tanti valori.
Quando usare HAVING
Per dati aggregati, quando devi filtrare in base ai risultati, HAVING è la scelta migliore. Ma se puoi spostare la condizione in WHERE (tipo un filtro su una colonna), la query sarà più semplice e veloce.
Esempio completo
Per fissare meglio il concetto, vediamo un altro esempio: selezionare i gruppi dove almeno uno studente ha un voto sotto 75, ma che non sono iscritti al corso "Filosofia".
Ricordiamo le nostre tabelle:
Tabella students
| id | name | group_id |
|---|---|---|
| 1 | Otto | 101 |
| 2 | Maria | 101 |
| 3 | Alex | 102 |
| 4 | Anna | 103 |
Tabella courses
| id | name |
|---|---|
| 1 | Matematica |
| 2 | Programmazione |
| 3 | Filosofia |
Tabella enrollments
| student_id | course_id | grade |
|---|---|---|
| 1 | 1 | 90 |
| 1 | 2 | NULL |
| 2 | 1 | 85 |
| 3 | 3 | 70 |
SELECT DISTINCT group_id
FROM students s
WHERE group_id IN (
SELECT s.group_id
FROM students s
JOIN enrollments e ON s.id = e.student_id
WHERE e.grade < 75
)
AND group_id NOT IN (
SELECT s.group_id -- subquery di primo livello
FROM students s
JOIN enrollments e ON s.id = e.student_id
WHERE e.course_id = (
SELECT id FROM courses WHERE name = 'Filosofia' -- subquery di secondo livello :P
)
);
- La prima subquery seleziona i gruppi dove c'è almeno uno studente con voto sotto 75.
- La seconda subquery esclude i gruppi collegati al corso "Filosofia".
- Combiniamo le condizioni con
INeNOT INper ottenere il risultato finale.
Risultato:
| group_id |
|---|
| 101 |
Quanto è utile tutto questo?
Nella vita reale questi approcci ti salvano quando devi analizzare relazioni complesse tra dati. Per esempio:
- Nell'analisi per individuare gruppi "speciali" di clienti (VIP, problematici ecc.).
- Nello sviluppo di sistemi di raccomandazione, dove filtri l'utente su tanti criteri.
- Ai colloqui, quando ti chiedono di ottimizzare una query SQL complessa.
Allenati! È la tua strada verso la maestria.
GO TO FULL VERSION