Funzioni di raggruppamento

// obiettivi di apprendimento
Usare le funzioni aggregate COUNT, SUM, AVG, MIN, MAX su insiemi di righe
Raggruppare i risultati con GROUP BY per produrre statistiche per categoria
Filtrare i gruppi con HAVING e distinguere il suo ruolo rispetto a WHERE
Applicare correttamente l’ordine logico di esecuzione: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
🎬
Video
Lezione video su GROUP BY, HAVING e funzioni aggregate
Guarda →
📄
Slides
Schema visivo dell’ordine di esecuzione e cheatsheet funzioni aggregate
Scarica →
⚗️
Lab
15 query di aggregazione da risolvere sul database scuola
GitHub →
🔗
Risorse
Documentazione MySQL sulle funzioni aggregate e GROUP BY
Vedi →

Dalle 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).

FunzioneCosa calcolaIgnora NULL?Esempio
COUNT(*)Numero di righe totali nel gruppoNo — conta tutte le righeCOUNT(*) → 42
COUNT(col)Numero di righe con valore non NULL nella colonna — salta i NULLCOUNT(email)
SUM(col)Somma dei valori numericiSUM(crediti)
AVG(col)Media aritmetica dei valori numericiAVG(voto)
MIN(col)Valore minimo (numeri, date, stringhe)MIN(data_nascita)
MAX(col)Valore massimoMAX(voto)
// COUNT(*) vs COUNT(colonna)

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.

// regola fondamentale di GROUP BY

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.

WHERE

Filtra le singole righe prima del raggruppamento. Può usare colonne normali. Non può usare funzioni aggregate.

HAVING

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:

1
FROM
Fonte dati
2
WHERE
Filtra righe
3
GROUP BY
Crea gruppi
4
HAVING
Filtra gruppi
5
SELECT
Proietta
6
ORDER BY
Ordina

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;
📌 Riepilogo — Punti chiave
  • 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 colonna
  • GROUP BY suddivide le righe in partizioni: ogni gruppo produce una riga nel risultato
  • In SELECT, con GROUP BY, puoi usare solo le colonne di raggruppamento e funzioni aggregate
  • WHERE filtra le righe prima del raggruppamento; HAVING filtra i gruppi dopo — non sono intercambiabili
  • Ordine logico: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT

Lascia un commento