🗃️ GROUP BY e HAVING
Guida completa con l'approccio delle "sottotabelle"
📊 1. La Tabella di Partenza
Per tutti gli esempi useremo questa tabella vendite (ampliata per mostrare casi complessi):
Modello logico
| id | venditore | categoria | prodotto | importo | quantita |
|---|---|---|---|---|---|
| 1 | Mario | Elettronica | Smartphone | 599.00 | 2 |
| 2 | Luigi | Abbigliamento | Giacca | 89.00 | 3 |
| 3 | Mario | Elettronica | Tablet | 450.00 | 1 |
| 4 | Anna | Abbigliamento | Scarpe | 120.00 | 2 |
| 5 | Luigi | Elettronica | Cuffie | 79.00 | 5 |
| 6 | Mario | Casa | Lampada | 45.00 | 4 |
| 7 | Anna | Elettronica | Mouse | 25.00 | 10 |
| 8 | Luigi | Casa | Cuscino | 35.00 | 6 |
| 9 | Mario | Abbigliamento | Maglietta | 29.00 | 8 |
| 10 | Anna | Casa | Vaso | 55.00 | 3 |
| 11 | Luigi | Elettronica | Tastiera | 65.00 | 2 |
| 12 | Mario | Elettronica | Monitor | 299.00 | 1 |
| 13 | Mario | Casa | Tappeto | 120.00 | 1 |
| 14 | Mario | Casa | Quadro | 80.00 | 2 |
| 15 | Mario | Abbigliamento | Cintura | 40.00 | 5 |
| 16 | Luigi | Abbigliamento | Sciarpa | 25.00 | 2 |
| 17 | Luigi | Abbigliamento | Guanti | 30.00 | 1 |
| 18 | Luigi | Casa | Sedia | 150.00 | 4 |
📦 2. GROUP BY - Dividere la Tabella in Sottotabelle
🤔 L'Esigenza
Guardando la tabella non sappiamo rispondere a una domanda molto naturale per chi gestisce le vendite: quanto ha incassato ogni venditore, e con quante vendite?
Vogliamo una riga per ogni venditore con accanto il totale incassato e il numero di vendite effettuate:
| venditore | totale_incassato | num_vendite |
|---|---|---|
| Mario | ? | ? |
| Luigi | ? | ? |
| Anna | ? | ? |
Non possiamo limitarci a SELECT venditore, importo FROM vendite: otterremmo
18 righe, una per vendita, non una per venditore. Ci serve una riga sola per venditore,
che riassuma tutte le sue vendite. Da qui nasce l'idea di GROUP BY.
🎯 L'IDEA CHIAVE
GROUP BY divide la tabella originale in tante SOTTOTABELLE,
una per ogni venditore.
Poi esegue i calcoli (SUM, COUNT, AVG, ecc.) separatamente su ogni sottotabella:
nel nostro caso SUM(importo) dà il totale incassato e COUNT(*) il numero di vendite.
La query che risponde all'esigenza
I valori che mancavano nella tabella di partenza sono proprio quelli prodotti dalle due funzioni di aggregazione applicate alle 3 sottotabelle (una sottotabella per ognuno dei 3 venditori):
SELECT venditore, SUM(importo) AS totale_incassato, COUNT(*) AS num_vendite
FROM vendite
GROUP BY venditore;
| venditore | totale_incassato | num_vendite |
|---|---|---|
| Mario | 1662.00 | 8 |
| Luigi | 473.00 | 7 |
| Anna | 200.00 | 3 |
Visualizziamo il Concetto
Quando scriviamo GROUP BY venditore, SQL prende la tabella e la "spezza" in sottotabelle:
| 👤 Sottotabella MARIO | |
| venditore | importo |
|---|---|
| Mario | 599.00 |
| Mario | 450.00 |
| Mario | 45.00 |
| Mario | 29.00 |
| Mario | 299.00 |
| Mario | 120.00 |
| Mario | 80.00 |
| Mario | 40.00 |
|
SUM = 1662.00 COUNT = 8 | |
| 👤 Sottotabella LUIGI | |
| venditore | importo |
|---|---|
| Luigi | 89.00 |
| Luigi | 79.00 |
| Luigi | 35.00 |
| Luigi | 65.00 |
| Luigi | 25.00 |
| Luigi | 30.00 |
| Luigi | 150.00 |
|
SUM = 473.00 COUNT = 7 | |
| 👤 Sottotabella ANNA | |
| venditore | importo |
|---|---|
| Anna | 120.00 |
| Anna | 25.00 |
| Anna | 55.00 |
|
SUM = 200.00 COUNT = 3 | |
📤 Risultato finale (output della Query):
| venditore | totale | num_vendite |
|---|---|---|
| Mario | 1662.00 | 8 |
| Luigi | 473.00 | 7 |
| Anna | 200.00 | 3 |
🔍 3. Esempi Pratici di GROUP BY
Esempio 1: Raggruppamento per Categoria
Domanda: "Quanto abbiamo venduto per ogni categoria?"
SELECT categoria, SUM(importo) AS totale, COUNT(*) AS num_vendite
FROM vendite
GROUP BY categoria;
Visualizziamo il Concetto
Anche qui GROUP BY categoria "spezza" la tabella in sottotabelle, una per ogni categoria:
| 📦 Sottotabella ELETTRONICA | |
| prodotto | importo |
|---|---|
| Smartphone | 599.00 |
| Tablet | 450.00 |
| Cuffie | 79.00 |
| Mouse | 25.00 |
| Tastiera | 65.00 |
| Monitor | 299.00 |
|
SUM = 1517.00 COUNT = 6 | |
| 📦 Sottotabella ABBIGLIAMENTO | |
| prodotto | importo |
|---|---|
| Giacca | 89.00 |
| Scarpe | 120.00 |
| Maglietta | 29.00 |
| Cintura | 40.00 |
| Sciarpa | 25.00 |
| Guanti | 30.00 |
|
SUM = 333.00 COUNT = 6 | |
| 📦 Sottotabella CASA | |
| prodotto | importo |
|---|---|
| Lampada | 45.00 |
| Cuscino | 35.00 |
| Vaso | 55.00 |
| Tappeto | 120.00 |
| Quadro | 80.00 |
| Sedia | 150.00 |
|
SUM = 485.00 COUNT = 6 | |
📤 Risultato finale (output della Query):
| categoria | totale | num_vendite |
|---|---|---|
| Elettronica | 1517.00 | 6 |
| Abbigliamento | 333.00 | 6 |
| Casa | 485.00 | 6 |
Esempio 2: GROUP BY su Due Colonne
Domanda: "Quanto ha venduto ogni venditore in ogni categoria?"
Qui le cose si fanno interessanti. Noi dobbiamo dividere la tabella in sottotabelle, una per ogni venditore e dopodiché ogni sottotabella va divisa in gruppi di righe, una per ogni categoria. E questo si ottiene con GROUP BY Venditore, Categoria .
Per visualizzarlo facilmente: immagina di avere una tabella per ogni venditore (Mario, Luigi, Anna) e, dentro questa tabella, le vendite vengono suddivise per colore in base alla categoria.
SELECT venditore, categoria, SUM(importo) AS totale
FROM vendite
GROUP BY venditore, categoria
ORDER BY venditore, categoria;
📦 Sottotabelle per Venditore (le righe sono colorate per Categoria):
Ogni blocco di colore uniforme verrà collassato in UNA sola riga nel risultato.
| 👤 Sottotabella MARIO | |
| Prodotto | Importo |
|---|---|
| Smartphone | 599.00 |
| Tablet | 450.00 |
| Monitor | 299.00 |
| Maglietta | 29.00 |
| Cintura | 40.00 |
| Lampada | 45.00 |
| Tappeto | 120.00 |
| Quadro | 80.00 |
| 👤 Sottotabella LUIGI | |
| Prodotto | Importo |
|---|---|
| Cuffie | 79.00 |
| Tastiera | 65.00 |
| Giacca | 89.00 |
| Sciarpa | 25.00 |
| Guanti | 30.00 |
| Cuscino | 35.00 |
| Sedia | 150.00 |
📤 Risultato finale (output della Query):
| venditore | categoria | totale |
|---|---|---|
| Anna | Abbigliamento | 120.00 |
| Anna | Casa | 55.00 |
| Anna | Elettronica | 25.00 |
| Luigi | Abbigliamento | 144.00 |
| Luigi | Casa | 185.00 |
| Luigi | Elettronica | 144.00 |
| Mario | Abbigliamento | 69.00 |
| Mario | Casa | 245.00 |
| Mario | Elettronica | 1348.00 |
🎯 4. HAVING - Selezionare Solo Alcune Sottotabelle
🤔 L'Esigenza
Con GROUP BY abbiamo ora una riga per ogni venditore, con accanto il suo
totale. Ma spesso non ci interessano tutte: per esempio vogliamo vedere
solo i venditori che hanno incassato più di 1000€.
Dobbiamo quindi scartare le sottotabelle dei venditori che hanno avuto un incasso totale minore o uguale a 1000:
| venditore | totale | supera 1000€? |
|---|---|---|
| Mario | 1662.00 | ? |
| Luigi | 473.00 | ? |
| Anna | 200.00 | ? |
Non possiamo farlo con WHERE: WHERE agisce sulle righe
prima che il GROUP BY suddivida la tabella in sottotabelle e non può usare
le funzioni di aggregazione come SUM(). Ci serve una condizione che si applichi
alle sottotabelle già formate, cioè al totale di ciascuna.
Da qui nasce l'idea di HAVING.
🎯 L'IDEA CHIAVE
HAVING è una condizione che FILTRA le sottotabelle.
Dopo che GROUP BY ha creato le sottotabelle e calcolato i risultati,
HAVING decide quali sottotabelle includere nel risultato finale
e quali escludere.
La query che risponde all'esigenza
SELECT venditore, SUM(importo) AS totale
FROM vendite
GROUP BY venditore
HAVING SUM(importo) > 1000;
Passo 1: GROUP BY crea le sottotabelle -- Se sposti il mouse sul rettangolo "ESCLUSA" vedrai i calcoli che hanno escluso la sottotabella
| 👤 MARIO | |
|
SUM = 1662.00 1662 > 1000 ✅ |
| 👤 LUIGI | |
|
SUM = 473.00 473 < 1000 ❌ |
| 👤 ANNA | |
|
SUM = 200.00 200 < 1000 ❌ |
📤 Risultato finale (output della Query) — solo le sottotabelle che passano il filtro:
| venditore | totale |
|---|---|
| Mario | 1662.00 |
Solo la sottotabella di Mario ha superato la condizione HAVING!
⚖️ 5. WHERE vs HAVING - La Differenza
WHERE
Filtra le RIGHE
prima di creare le sottotabelle
- Agisce sulla tabella originale
- Decide quali righe entrano nelle sottotabelle
- NON può usare funzioni aggregate
HAVING
Filtra le SOTTOTABELLE
dopo averle create
- Agisce sui gruppi già formati
- Decide quali sottotabelle includere nel risultato
- PUÒ usare funzioni aggregate
Esempio Pratico: WHERE + HAVING insieme
Domanda: "Per le vendite sopra i 50€, mostrami i venditori con almeno 2 vendite"
SELECT venditore, COUNT(*) AS vendite, SUM(importo) AS totale
FROM vendite
WHERE importo > 50 -- Filtra RIGHE prima del grouping
GROUP BY venditore
HAVING COUNT(*) >= 2; -- Filtra SOTTOTABELLE dopo il grouping
Prima vengono escluse le righe con importo ≤ 50.
Le righe rimanenti vengono divise in sottotabelle per venditore.
Vengono incluse solo le sottotabelle con almeno 2 righe valide.
📤 Risultato finale (output della Query):
| venditore | vendite | totale |
|---|---|---|
| Mario | 5 | 1548.00 |
| Luigi | 4 | 383.00 |
| Anna | 2 | 175.00 |
🔄 6. Ordine: Scrittura vs Esecuzione
Spesso si fa confusione perché l'ordine in cui SCRIVI i comandi è diverso dall'ordine in cui SQL li ESEGUE.
(Come lo scrivi)
(Come viene eseguito)
🧐 Il Paradosso del SELECT
Hai notato? Anche se SELECT è la prima parola che scrivi, logicamente è la quinta operazione eseguita!
Ecco perché SELECT è quasi alla fine: il database deve prima trovare i dati (FROM), filtrarli (WHERE), raggrupparli (GROUP BY) e filtrare i gruppi (HAVING). Solo a quel punto decide quali colonne mostrarti e calcola i risultati finali.
E infine, solo dopo aver "selezionato" i dati finali, può ordinarli (ORDER BY).
📋 Esempio: Ordine di Scrittura vs Esecuzione
🎯 7. Esempi Completi
Esempio 1: Report Venditori
Domanda: "Mostra i venditori con più di 250€ venduti, ordinati dal migliore"
SELECT
venditore,
COUNT(*) AS vendite,
SUM(importo) AS totale,
ROUND(AVG(importo), 2) AS media
FROM vendite
GROUP BY venditore
HAVING SUM(importo) > 250
ORDER BY totale DESC;
📤 Risultato finale (output della Query):
| venditore | vendite | totale | media |
|---|---|---|---|
| Mario | 8 | 1662.00 | 207.75 |
| Luigi | 7 | 473.00 | 67.57 |
Esempio 2: Combinazioni Venditore-Categoria
Domanda: "Trova le combinazioni venditore+categoria con totale > 100€"
SELECT
venditore,
categoria,
SUM(importo) AS totale,
SUM(quantita) AS pezzi
FROM vendite
GROUP BY venditore, categoria
HAVING SUM(importo) > 100
ORDER BY totale DESC;
📤 Risultato finale (output della Query):
| venditore | categoria | totale | pezzi |
|---|---|---|---|
| Mario | Elettronica | 1348.00 | 4 |
| Mario | Casa | 245.00 | 7 |
| Luigi | Casa | 185.00 | 10 |
| Luigi | Abbigliamento | 144.00 | 6 |
| Luigi | Elettronica | 144.00 | 7 |
| Anna | Abbigliamento | 120.00 | 2 |
⚠️ 8. Errori Comuni da Evitare
Errore 1: Colonne non aggregate nel SELECT
❌ SBAGLIATO
SELECT venditore, prodotto -- ❌ Errore!
FROM vendite
GROUP BY venditore;
prodotto non è nel GROUP BY. In una sottotabella di Mario ci sono 8 prodotti diversi: quale mostrare?
✅ CORRETTO
SELECT venditore, SUM(importo) AS totale
FROM vendite
GROUP BY venditore;
Nel SELECT usa solo colonne presenti nel GROUP BY oppure funzioni aggregate.
📝 9. Esercizi
Esercizio 1 - Base
Conta quante vendite sono state fatte per ogni categoria.
Mostra Soluzione
SELECT categoria, COUNT(*) AS num_vendite
FROM vendite
GROUP BY categoria;
Esercizio 2 - HAVING
Trova i venditori che hanno venduto più di 15 pezzi totali.
Mostra Soluzione
SELECT venditore, SUM(quantita) AS pezzi
FROM vendite
GROUP BY venditore
HAVING SUM(quantita) > 15;
Esercizio 3 - WHERE + HAVING
Per le vendite di importo superiore a 40€, trova le categorie con almeno 2 vendite.
Mostra Soluzione
SELECT categoria, COUNT(*) AS vendite
FROM vendite
WHERE importo > 40
GROUP BY categoria
HAVING COUNT(*) >= 2;
Esercizio 4 - Avanzato
Trova le combinazioni venditore+categoria dove la media per vendita supera i 100€ (mostra la media arrotondata a due decimali).
Mostra Soluzione
SELECT venditore, categoria, ROUND(AVG(importo), 2) AS media
FROM vendite
GROUP BY venditore, categoria
HAVING AVG(importo) > 100;
📌 Riepilogo Finale
GROUP BY
Divide la tabella in SOTTOTABELLE
(una per ogni valore unico della colonna)
e calcola le funzioni aggregate su ciascuna
HAVING
Filtra le SOTTOTABELLE
Include nel risultato solo le sottotabelle
che soddisfano la condizione specificata
Schema Mentale
-- 1. WHERE filtra le RIGHE della tabella originale
-- 2. GROUP BY crea le SOTTOTABELLE
-- 3. HAVING decide quali SOTTOTABELLE tenere
Scrivi la query e il trainer la esegue davvero, raggruppa le righe come spiega la guida e ti dice se il risultato è corretto. Gli esercizi usano solo le clausole di questa pagina (sezioni 2–7) e una sola tabella per volta.