COUNT, SUM, AVG, MIN, MAX su insiemi di righeGROUP BY per produrre statistiche per categoriaHAVING e distinguere il suo ruolo rispetto a WHEREDalle righe ai gruppi: il concetto di aggregazione
Finora le query restituivano una riga per ogni riga della tabella. Ma spesso ci interessano domande diverse: quanti studenti ci sono per anno? Qual è la media dei voti per corso? Qual è il voto massimo per ogni docente?
Queste sono domande di aggregazione: non lavoriamo su singole righe, ma su gruppi di righe. SQL risponde con due strumenti combinati: le funzioni aggregate e la clausola GROUP BY.
Le funzioni aggregate
Le funzioni aggregate operano su un insieme di valori e restituiscono un unico valore per tutto l’insieme (o per ogni gruppo, se usi GROUP BY).
| Funzione | Cosa calcola | Ignora NULL? | Esempio |
|---|---|---|---|
COUNT(*) | Numero di righe totali nel gruppo | No — conta tutte le righe | COUNT(*) → 42 |
COUNT(col) | Numero di righe con valore non NULL nella colonna | Sì — salta i NULL | COUNT(email) |
SUM(col) | Somma dei valori numerici | Sì | SUM(crediti) |
AVG(col) | Media aritmetica dei valori numerici | Sì | AVG(voto) |
MIN(col) | Valore minimo (numeri, date, stringhe) | Sì | MIN(data_nascita) |
MAX(col) | Valore massimo | Sì | MAX(voto) |
COUNT(*) conta tutte le righe incluse quelle con NULL. COUNT(email) conta solo le righe dove email non è NULL. La differenza è importante quando vuoi sapere quanti studenti hanno un’email registrata.
Funzioni aggregate senza GROUP BY
Se usi una funzione aggregate senza GROUP BY, viene applicata a tutte le righe della tabella (o quelle filtrate dalla WHERE) e restituisce una sola riga:
-- statistiche globali sugli esami SELECT COUNT(*) AS totale_esami, COUNT(voto) AS esami_con_voto, AVG(voto) AS media_globale, MIN(voto) AS voto_minimo, MAX(voto) AS voto_massimo FROM esame; -- media degli esami del primo anno SELECT AVG(voto) AS media_primo_anno FROM esame WHERE anno_accademico = 2024;
GROUP BY: raggruppare per categoria
GROUP BY divide le righe in partizioni (gruppi) in base ai valori uguali delle colonne specificate. Le funzioni aggregate vengono poi calcolate indipendentemente per ogni gruppo.
Nella clausola SELECT, puoi mettere solo: le colonne che compaiono nella GROUP BY, oppure funzioni aggregate. Qualsiasi altra colonna causerebbe ambiguità (quale valore dovrebbe apparire se il gruppo ha 50 righe diverse?)
-- numero di studenti per anno di nascita SELECT YEAR(data_nascita) AS anno_nascita, COUNT(*) AS num_studenti FROM studente GROUP BY YEAR(data_nascita) ORDER BY anno_nascita; -- media voti per ogni corso SELECT corso_id, AVG(voto) AS media_voto, COUNT(*) AS num_esami FROM esame GROUP BY corso_id ORDER BY media_voto DESC; -- crediti totali per anno di corso SELECT anno, SUM(crediti) AS crediti_totali, COUNT(*) AS num_corsi FROM corso GROUP BY anno ORDER BY anno;
GROUP BY su più colonne
Puoi raggruppare per combinazioni di più colonne. Il gruppo è definito dalla combinazione unica di tutti i valori delle colonne specificate:
-- media voti per corso E per anno accademico SELECT corso_id, anno_accademico, AVG(voto) AS media FROM esame GROUP BY corso_id, anno_accademico ORDER BY corso_id, anno_accademico;
HAVING: filtrare i gruppi
Dopo il raggruppamento, potresti voler tenere solo i gruppi che soddisfano una certa condizione aggregata. Qui entra HAVING.
Filtra le singole righe prima del raggruppamento. Può usare colonne normali. Non può usare funzioni aggregate.
Filtra i gruppi dopo il raggruppamento. Può usare funzioni aggregate. Non conviene usarla su colonne non aggregate (usa WHERE per quello).
-- corsi con più di 5 esami sostenuti SELECT corso_id, COUNT(*) AS num_esami FROM esame GROUP BY corso_id HAVING COUNT(*) > 5 ORDER BY num_esami DESC; -- corsi con media voti sotto 22 (da monitorare) SELECT corso_id, AVG(voto) AS media FROM esame GROUP BY corso_id HAVING AVG(voto) < 22; -- errore tipico: usare HAVING invece di WHERE per filtrare righe -- ❌ Inefficiente (filtra dopo il raggruppamento): SELECT corso_id, COUNT(*) FROM esame GROUP BY corso_id HAVING corso_id > 10; -- ✅ Corretto (filtra le righe prima, poi raggruppa): SELECT corso_id, COUNT(*) FROM esame WHERE corso_id > 10 GROUP BY corso_id;
Ordine logico di esecuzione completo
Aggiorniamo la pipeline di esecuzione con le nuove clausole:
Esempi pratici completi
-- 1. Quanti esami ha sostenuto ogni studente? SELECT studente_id, COUNT(*) AS num_esami FROM esame GROUP BY studente_id ORDER BY num_esami DESC; -- 2. Media, minimo e massimo dei voti per corso (solo esami superati ≥18) SELECT corso_id, ROUND(AVG(voto), 2) AS media, MIN(voto) AS minimo, MAX(voto) AS massimo, COUNT(*) AS n FROM esame WHERE voto >= 18 GROUP BY corso_id ORDER BY media DESC; -- 3. Corsi con più di 20 studenti iscritti SELECT corso_id, COUNT(DISTINCT studente_id) AS iscritti FROM esame GROUP BY corso_id HAVING COUNT(DISTINCT studente_id) > 20; -- 4. Anno di corso con il maggior numero di crediti totali SELECT anno, SUM(crediti) AS totale_crediti FROM corso GROUP BY anno ORDER BY totale_crediti DESC LIMIT 1; -- 5. Studenti con almeno 3 esami superati con voto >= 27 SELECT studente_id, COUNT(*) AS esami_alti FROM esame WHERE voto >= 27 GROUP BY studente_id HAVING COUNT(*) >= 3 ORDER BY esami_alti DESC;
- Le funzioni aggregate (
COUNT,SUM,AVG,MIN,MAX) collassano più righe in un unico valore COUNT(*)conta tutte le righe;COUNT(col)salta i NULL della colonnaGROUP BYsuddivide le righe in partizioni: ogni gruppo produce una riga nel risultato- In
SELECT, conGROUP BY, puoi usare solo le colonne di raggruppamento e funzioni aggregate WHEREfiltra le righe prima del raggruppamento;HAVINGfiltra i gruppi dopo — non sono intercambiabili- Ordine logico: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT