A volte non basta solo raggruppare i dati e filtrare il risultato, ma serve farlo con una logica extra — tipo confrontare la media dei voti degli studenti in un gruppo con qualche criterio esterno. Qui entra in gioco HAVING con le subquery — uno strumento potente che ti aiuta a prendere decisioni più intelligenti direttamente nella query SQL.
Ripassiamo HAVING
Concentriamoci sulle subquery usate insieme a HAVING, per filtrare i dati a livello di valori aggregati. Perché? Se WHERE ti permette di filtrare le singole righe, HAVING si applica ai dati già raggruppati — è un altro livello di analisi che amplia le tue possibilità.
Prima di tuffarci nella combinazione di subquery e HAVING, rinfreschiamoci la memoria su cosa sia HAVING e come si differenzia da WHERE.
WHEREfiltra le righe prima del raggruppamento (GROUP BY).HAVINGfiltra i dati dopo l’aggregazione, quando i dati sono già raggruppati.
Immagina di analizzare studenti e i loro voti. Con WHERE puoi escludere studenti con certi voti minimi, mentre HAVING ti permette di escludere interi gruppi di studenti in base alla loro media o al voto massimo.
Esempio di dati
Ecco una tabella di esempio con studenti:
Tabella students:
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
Esempio di utilizzo di HAVING (senza subquery)
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > 75;
Risultato:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
| Math | 75.0 |
Il dipartimento "History" non è stato selezionato perché la sua media è sotto 75. Facile, no? Ora aggiungiamo un po’ di magia con le subquery. Nel prossimo esempio possiamo, ad esempio, filtrare confrontando con la media generale di tutti i dipartimenti.
Subquery in HAVING
Le subquery in HAVING sono una figata per aggiungere flessibilità quando filtri dati aggregati. Ti permettono di confrontare aggregati, come la media o il massimo, con valori calcolati da altre parti del database. In parole povere, puoi chiederti: "Il nostro risultato è meglio della media generale?"
Esempio: filtrare dipartimenti per media voti
Supponiamo di voler trovare quei dipartimenti dove gli studenti vanno meglio degli altri — cioè la media dei voti del dipartimento è più alta della media dell’università.
Ecco i nostri dati:
Tabella students:
| student_id | student_name | department | grade |
|---|---|---|---|
| 1 | Alex | Physics | 80 |
| 2 | Maria | Physics | 85 |
| 3 | Dan | Math | 90 |
| 4 | Lisa | Math | 60 |
| 5 | John | History | 70 |
Per prima cosa otteniamo la media di tutti gli studenti:
SELECT AVG(grade) AS university_avg
FROM students;
Ora usiamo una subquery in HAVING:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Risultato:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
Cosa succede qui?
- La subquery (
SELECT AVG(grade) FROM students) calcola la media generale — in questo caso è 77. - La query principale raggruppa gli studenti per dipartimento e calcola la media per ciascuno.
HAVINGconfronta la media del dipartimento con la media generale e tiene solo quelli sopra.
Confronto tra WHERE e HAVING
Per capire la differenza, immaginiamo che vuoi selezionare solo quegli studenti che hanno voti sopra la media. Questo si fa solo con WHERE:
SELECT name, grade
FROM students
WHERE grade > (SELECT AVG(grade) FROM students);
Risultato (usando la tabella degli esempi precedenti):
| name | grade |
|---|---|
| Alex | 80 |
| Maria | 85 |
| Dan | 90 |
Ma se vuoi vedere in quali dipartimenti la media degli studenti è sopra la media dell’università, senza HAVING non ce la fai — perché stai filtrando gruppi, non righe:
SELECT department, AVG(grade) AS avg_grade
FROM students
GROUP BY department
HAVING AVG(grade) > (SELECT AVG(grade) FROM students);
Risultato:
| department | avg_grade |
|---|---|
| Physics | 82.5 |
In breve:
WHERElavora sulle singole righe prima del raggruppamento.HAVINGfiltra i gruppi dopo che sono stati aggregati.
Esempio: lavorare con più aggregati
Vediamo un altro caso. Supponiamo di avere la tabella students con i voti degli studenti e i loro dipartimenti:
Tabella students:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Ora vogliamo trovare i dipartimenti dove:
- La media dei voti degli studenti è più alta della media dell’università.
- Il voto massimo nel dipartimento è sopra 90.
Scriviamo questa query:
SELECT department, AVG(grade) AS avg_grade, MAX(grade) AS max_grade
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND MAX(grade) > 90;
Cosa succede in questa query:
AVG(grade)> (SELECT AVG(grade) FROM students) — controlliamo che il dipartimento sia mediamente più forte degli altri.MAX(grade)> 90 — vuol dire che lì c’è qualcuno che ha spaccato l’esame.
Risultato:
| department | avg_grade | max_grade |
|---|---|---|
| Math | 92.5 | 95 |
Il dipartimento "Math" è l’unico che ha sia una media sopra la generale che uno studente top con voto sopra 90.
Esempio: selezionare gruppi con deviazione minima
Supponiamo che vuoi trovare i gruppi dove la differenza tra il voto massimo e minimo degli studenti è minore rispetto alla differenza dell’università in generale.
Ecco la tabella students con cui lavoriamo:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
Dividiamo il compito in step:
- Prima calcoliamo la differenza max-min per tutta l’università:
SELECT MAX(grade) - MIN(grade) AS range_university FROM students; - Ora creiamo la query principale e la uniamo a questa subquery:
SELECT department, MAX(grade) - MIN(grade) AS range_department
FROM students
GROUP BY department
HAVING (MAX(grade) - MIN(grade)) < ( SELECT MAX(grade) - MIN(grade) FROM students );
Risultato della query:
| department | range_department |
|---|---|
| Physics | 5 |
| Math | 5 |
I gruppi "Physics" e "Math" hanno mostrato voti più stabili — la loro differenza è minore rispetto all’università in generale.
Ottimizzazione delle query con HAVING e subquery
Ricorda che le subquery annidate possono avere un impatto serio sulle performance, soprattutto su database grandi. Ecco qualche consiglio:
Usa gli indici. Se la subquery lavora su una colonna usata in WHERE o JOIN, assicurati che quella colonna abbia un indice.
Evita l’overflow di dati. Se la subquery restituisce troppi risultati intermedi, spezzala in step o usa tabelle temporanee.
Profiling delle query con EXPLAIN. Controlla sempre come PostgreSQL esegue la tua query. Se vedi che la subquery viene eseguita troppe volte, pensa a come ottimizzarla.
Confronta con CTE. In certi casi usare WITH (Common Table Expressions) può essere più veloce e leggibile. Ma di questo ne parliamo nelle prossime lezioni :P
Combinare subquery, HAVING e GROUP BY
Con le subquery in HAVING puoi costruire filtri più complessi, soprattutto quando devi considerare aggregati, medie e altre metriche insieme. Tutto questo ti aiuta a trovare insight interessanti nei dati reali.
Esempio: confronto tra dipartimenti per media voti e numero di studenti
Supponiamo che vuoi selezionare i dipartimenti dove:
- La media dei voti è sopra la media dell’università.
- Il numero di studenti è maggiore di quello del dipartimento con la media più bassa.
Ecco la tabella di partenza students:
| name | grade | department |
|---|---|---|
| Alex | 80 | Physics |
| Maria | 85 | Physics |
| Dan | 90 | Math |
| Olga | 95 | Math |
| Ivan | 70 | History |
| Nina | 75 | History |
| Oleg | 60 | History |
Query:
SELECT department, AVG(grade) AS avg_grade, COUNT(*) AS student_count
FROM students
GROUP BY department
HAVING AVG(grade) > ( SELECT AVG(grade) FROM students )
AND COUNT(*) > (
SELECT COUNT(*)
FROM students
GROUP BY department
ORDER BY AVG(grade)
LIMIT 1
);
Questa query mostra come combinare subquery in HAVING e GROUP BY per analizzare più criteri insieme. Risultato:
| department | avg_grade | student_count |
|---|---|---|
| Physics | 82.5 | 2 |
| Math | 92.5 | 2 |
Il dipartimento History non è stato selezionato perché ha la media più bassa e il minor numero di studenti. Physics e Math — entrambi sopra la media sia per voti che per numero di studenti.
Errori tipici e come evitarli
Errore con NULL. Se i dati contengono NULL, le subquery con HAVING possono dare risultati strani. Usa COALESCE per gestire questi casi:
SELECT AVG(grade)
FROM students
WHERE grade IS NOT NULL;
Dati ridondanti nella subquery. Se la subquery restituisce troppi risultati, le performance ne risentono. Specifica sempre bene le condizioni della subquery.
Non capire l’ordine di esecuzione. Ricorda che HAVING viene eseguito dopo il raggruppamento, mentre le subquery possono essere eseguite prima della query principale.
Mancanza di indici. Se le colonne usate nella subquery non sono indicizzate, la query sarà molto più lenta.
Le subquery in HAVING ti aprono un sacco di possibilità per analizzare i dati a livello di aggregati. Puoi filtrare gruppi con condizioni complesse, confrontare risultati tra gruppi e creare query analitiche avanzate. Congratulazioni, ora sei pronto per usare queste skill nei progetti veri!
GO TO FULL VERSION