SQL GROUP BY e Funzioni di Aggregazione: Riassumi i Tuoi Dati
Published: 2026-04-08
GROUP BY comprime più righe che condividono lo stesso valore in una o più colonne in un’unica riga di riepilogo. Combinato con funzioni di aggregazione come COUNT, SUM, AVG, MIN e MAX, è la base di quasi tutte le query di reporting e analytics. GROUP BY è supportato in tutti i database SQL, inclusi SQLite, PostgreSQL, MySQL, SQL Server e Oracle.
Dati di esempio
Usiamo due tabelle in tutti gli esempi:
Tabella employees:
| id | name | department | hire_date | salary |
|---|---|---|---|---|
| 1 | Alice | Engineering | 2023-01-15 | 95000 |
| 2 | Bob | Engineering | 2023-06-01 | 85000 |
| 3 | Carol | Engineering | 2024-03-10 | 85000 |
| 4 | Dave | Sales | 2023-02-20 | 72000 |
| 5 | Eve | Sales | 2023-09-05 | 68000 |
| 6 | Frank | Sales | 2024-01-15 | 65000 |
| 7 | Grace | Marketing | 2023-04-01 | 82000 |
| 8 | Henry | Marketing | 2024-06-01 | 70000 |
Tabella orders:
| id | customer | product | category | quantity | unit_price | order_date | status |
|---|---|---|---|---|---|---|---|
| 1 | Acme Corp | Laptop Pro | Electronics | 3 | 1299.99 | 2026-01-05 | delivered |
| 2 | Acme Corp | Wireless Mouse | Electronics | 10 | 29.99 | 2026-01-12 | delivered |
| 3 | Globex | Office Chair | Furniture | 5 | 249.99 | 2026-01-18 | delivered |
| 4 | Globex | Standing Desk | Furniture | 2 | 599.99 | 2026-01-20 | shipped |
| 5 | Initech | Notebook Pack | Stationery | 20 | 12.99 | 2026-02-03 | delivered |
| 6 | Initech | Pen Set | Stationery | 15 | 8.99 | 2026-02-07 | delivered |
| 7 | Acme Corp | Monitor 27" | Electronics | 4 | 399.99 | 2026-02-14 | delivered |
| 8 | Umbrella Ltd | Laptop Pro | Electronics | 2 | 1299.99 | 2026-02-20 | cancelled |
| 9 | Umbrella Ltd | Desk Lamp | Furniture | 8 | 39.99 | 2026-03-01 | delivered |
| 10 | Globex | Wireless Mouse | Electronics | 12 | 29.99 | 2026-03-05 | shipped |
| 11 | Initech | Monitor 27" | Electronics | 1 | 399.99 | 2026-03-10 | delivered |
| 12 | Acme Corp | Standing Desk | Furniture | 1 | 599.99 | 2026-03-15 | processing |
Sintassi di GROUP BY
SELECT column1, aggregate_function(column2)
FROM table_name
WHERE condition
GROUP BY column1
HAVING aggregate_condition
ORDER BY column1;
- GROUP BY — una o più colonne le cui combinazioni di valori distinti definiscono ciascun gruppo
- Funzioni di aggregazione — calcolano un singolo valore da tutte le righe del gruppo
- HAVING — filtra i gruppi (come WHERE, ma per i risultati raggruppati)
- Ogni colonna in SELECT che non è all’interno di un’aggregazione deve comparire in GROUP BY
Ordine di esecuzione SQL
Le clausole SQL sono scritte in un certo ordine ma eseguite in un ordine diverso. Capirlo aiuta a spiegare perché HAVING può fare riferimento a funzioni di aggregazione ma WHERE no, e perché non puoi usare un alias di SELECT in una clausola HAVING.
FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY
| Step | Clause | Cosa succede |
|---|---|---|
| 1 | FROM | Carica le tabelle e valuta eventuali JOIN |
| 2 | WHERE | Filtra le singole righe — le funzioni di aggregazione non sono permesse qui |
| 3 | GROUP BY | Comprimi le righe rimanenti in gruppi |
| 4 | HAVING | Filtra i gruppi — le funzioni di aggregazione sono permesse qui |
| 5 | SELECT | Calcola le colonne di output e gli alias |
| 6 | ORDER BY | Ordina il result set finale |
Per questo WHERE status = 'delivered' viene eseguito prima del raggruppamento (filtro veloce a livello di riga), mentre HAVING COUNT(*) > 2 viene eseguito dopo il raggruppamento (filtra interi gruppi).
COUNT()
COUNT(*) conta tutte le righe in un gruppo. COUNT(column) conta i valori non NULL in quella colonna.
Esempio: Numero di dipendenti per reparto
Esempio: Numero di ordini per cliente
Esempio: Conteggio ordini consegnati vs altri stati
Conta solo gli ordini che corrispondono a una condizione usando COUNT con un’espressione CASE:
SUM()
SUM(column) somma tutti i valori nel gruppo per una colonna numerica.
Esempio: Salario totale per reparto
Esempio: Fatturato totale per categoria
Calcola il fatturato come quantity * unit_price:
Esempio: Fatturato per cliente e categoria
Raggruppa per più colonne per suddividere ulteriormente il fatturato:
AVG()
AVG(column) restituisce la media aritmetica di tutti i valori non NULL nel gruppo.
Esempio: Salario medio per reparto
Esempio: Valore medio dell’ordine per cliente
MIN() e MAX()
MIN(column) e MAX(column) restituiscono rispettivamente il valore più piccolo e più grande nel gruppo.
Esempio: Intervallo di salari per reparto
Esempio: Prima e ultima data d’ordine per cliente
Combinare più aggregazioni
Puoi combinare qualsiasi numero di funzioni di aggregazione in un’unica query.
Esempio: Riepilogo completo del reparto
Esempio: Riepilogo ordini per cliente
HAVING
HAVING filtra i gruppi dopo l’aggregazione — è l’equivalente di WHERE per i risultati raggruppati.
Esempio: Reparti con più di due dipendenti
Esempio: Clienti con fatturato totale superiore a 2000
Esempio: Categorie con valore medio dell’ordine inferiore a 500
WHERE vs. HAVING
WHERE filtra le singole righe prima del raggruppamento. HAVING filtra i gruppi dopo l’aggregazione.
Esempio: Fatturato solo dagli ordini consegnati per cliente
Usa WHERE per escludere righe prima del raggruppamento, poi HAVING per filtrare i risultati:
GROUP BY con JOIN
Puoi raggruppare dati che coprono più tabelle effettuando prima una join.
Esempio: Fatturato per reparto tramite join con employees
Esempio: Conteggio ordini e fatturato per categoria e mese
GROUP BY con espressioni
Puoi raggruppare per espressioni calcolate, non solo per colonne semplici.
Esempio: Dipendenti assunti per anno
Esempio: Ordini per mese
COUNT(DISTINCT)
COUNT(DISTINCT column) conta il numero di valori unici nel gruppo.
Esempio: Prodotti unici ordinati per cliente
Filtrare gruppi con più condizioni
HAVING supporta qualsiasi espressione booleana, incluse combinazioni AND/OR.
Esempio: Clienti attivi di alto valore
Trova i clienti con più di un ordine e fatturato totale superiore a 1000:
Esempio pratico: Report di vendita
Combina tutto in un report di riepilogo vendite completo.
Esempio: Report sulle performance per categoria
Esempio: Prodotto top per fatturato
GROUP BY vs. funzioni finestra (window functions)
GROUP BY comprime le righe in una riga per gruppo. Le funzioni finestra mantengono tutte le righe e aggiungono una colonna calcolata — vedi il tutorial sulle funzioni finestra SQL per i dettagli.
Esempio: GROUP BY restituisce una riga per reparto
Esempio: La funzione finestra mantiene tutte le righe
Casi d’uso comuni per GROUP BY
- Dashboard di reporting — Riepiloga totali, medie e conteggi per categoria o periodo di tempo
- Analisi dell’organico — Conta dipendenti, utenti o record per gruppo
- Suddivisione del fatturato — Totale e media del fatturato per prodotto, area geografica o cliente
- Analisi dei trend — Raggruppa per mese o anno per individuare pattern nel tempo
- Top-N per gruppo — Combina con subquery o CTE per trovare il migliore in ogni gruppo
- Validazione dei dati — Conta duplicati o valori NULL tra i gruppi
- Analisi di coorte — Raggruppa gli utenti per data di registrazione o segmento di comportamento
Suggerimenti sulle prestazioni
- Indicizza le colonne di GROUP BY — Gli indici sulle colonne di raggruppamento velocizzano la fase di ordinamento e grouping
- Filtra presto con WHERE — Ridurre le righe prima del raggruppamento con WHERE è più veloce che filtrare dopo con HAVING
- Evita SELECT * — Seleziona solo le colonne di cui hai davvero bisogno; le operazioni di aggregazione beneficiano di dati più “stretti”
- Usa indici coprenti (covering indexes) — Un indice che include sia la colonna di GROUP BY sia la colonna aggregata può evitare una scansione completa della tabella
- Valuta viste materializzate — Per aggregazioni calcolate spesso su tabelle grandi, alcuni database supportano riepiloghi pre-calcolati
Riepilogo
- GROUP BY comprime le righe che condividono gli stessi valori di colonna in una riga di riepilogo per ogni combinazione unica
- COUNT(*) conta tutte le righe; COUNT(column) conta i valori non NULL; COUNT(DISTINCT column) conta i valori unici
- SUM totalizza i valori numerici; AVG calcola la media; MIN / MAX trovano i valori limite
- Ogni colonna in SELECT deve comparire in GROUP BY oppure essere racchiusa in una funzione di aggregazione
- WHERE filtra le righe prima del raggruppamento; HAVING filtra i gruppi dopo l’aggregazione
- Puoi fare GROUP BY su espressioni (ad es.
SUBSTR(date, 1, 7)per un raggruppamento a livello di mese) - Effettuare join tra tabelle prima del raggruppamento ti permette di combinare dati da più fonti in un unico report
- Usa le funzioni finestra invece di GROUP BY quando ti servono metriche a livello di gruppo senza perdere le singole righe
Prova a combinare GROUP BY con JOIN, espressioni CASE e clausole HAVING per costruire i report di cui hai bisogno per l’analisi dei dati!