🗃️ 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

vendite(id(PK), venditore, categoria, prodotto, importo, quantita)
🗄️ Tabella: vendite
Tabella vendite: dati di esempio usati in tutti gli esempi
id venditore categoria prodotto importo quantita
1MarioElettronicaSmartphone599.002
2LuigiAbbigliamentoGiacca89.003
3MarioElettronicaTablet450.001
4AnnaAbbigliamentoScarpe120.002
5LuigiElettronicaCuffie79.005
6MarioCasaLampada45.004
7AnnaElettronicaMouse25.0010
8LuigiCasaCuscino35.006
9MarioAbbigliamentoMaglietta29.008
10AnnaCasaVaso55.003
11LuigiElettronicaTastiera65.002
12MarioElettronicaMonitor299.001
13MarioCasaTappeto120.001
14MarioCasaQuadro80.002
15MarioAbbigliamentoCintura40.005
16LuigiAbbigliamentoSciarpa25.002
17LuigiAbbigliamentoGuanti30.001
18LuigiCasaSedia150.004

📦 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:

Risultato atteso: totale incassato e numero di vendite per ogni venditore
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;
Risultato della query: totale incassato e numero di vendite per ogni venditore
venditore totale_incassato num_vendite
Mario1662.008
Luigi473.007
Anna200.003

Visualizziamo il Concetto

Quando scriviamo GROUP BY venditore, SQL prende la tabella e la "spezza" in sottotabelle:

👤 Sottotabella MARIO
venditoreimporto
Mario599.00
Mario450.00
Mario45.00
Mario29.00
Mario299.00
Mario120.00
Mario80.00
Mario40.00
👤 Sottotabella LUIGI
venditoreimporto
Luigi89.00
Luigi79.00
Luigi35.00
Luigi65.00
Luigi25.00
Luigi30.00
Luigi150.00
👤 Sottotabella ANNA
venditoreimporto
Anna120.00
Anna25.00
Anna55.00
⬇️
Una riga per ogni sottotabella
⬇️

📤 Risultato finale (output della Query):

Risultato finale: totale incassato e numero di vendite per ogni venditore
venditore totale num_vendite
Mario1662.008
Luigi473.007
Anna200.003

🔍 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
prodottoimporto
Smartphone599.00
Tablet450.00
Cuffie79.00
Mouse25.00
Tastiera65.00
Monitor299.00
📦 Sottotabella ABBIGLIAMENTO
prodottoimporto
Giacca89.00
Scarpe120.00
Maglietta29.00
Cintura40.00
Sciarpa25.00
Guanti30.00
📦 Sottotabella CASA
prodottoimporto
Lampada45.00
Cuscino35.00
Vaso55.00
Tappeto120.00
Quadro80.00
Sedia150.00
⬇️
Una riga per ogni sottotabella
⬇️

📤 Risultato finale (output della Query):

Risultato della query: totale e numero di vendite per ogni categoria
categoria totale num_vendite
Elettronica1517.006
Abbigliamento333.006
Casa485.006

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.

⚡ Elettronica
👕 Abbigliamento
🏠 Casa
👤 Sottotabella MARIO
ProdottoImporto
Smartphone599.00
Tablet450.00
Monitor299.00
Maglietta29.00
Cintura40.00
Lampada45.00
Tappeto120.00
Quadro80.00
👤 Sottotabella LUIGI
ProdottoImporto
Cuffie79.00
Tastiera65.00
Giacca89.00
Sciarpa25.00
Guanti30.00
Cuscino35.00
Sedia150.00

📤 Risultato finale (output della Query):

Risultato della query: totale per ogni combinazione venditore-categoria
venditore categoria totale
AnnaAbbigliamento120.00
AnnaCasa55.00
AnnaElettronica25.00
LuigiAbbigliamento144.00
LuigiCasa185.00
LuigiElettronica144.00
MarioAbbigliamento69.00
MarioCasa245.00
MarioElettronica1348.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:

Sottotabelle dei venditori: quali superano la soglia di 1000€?
venditore totale supera 1000€?
Mario1662.00?
Luigi473.00?
Anna200.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
👤 LUIGI
👤 ANNA

📤 Risultato finale (output della Query) — solo le sottotabelle che passano il filtro:

Risultato della query: solo il venditore con totale superiore a 1000€
venditore totale
Mario1662.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
1
WHERE importo > 50

Prima vengono escluse le righe con importo ≤ 50.

2
GROUP BY venditore

Le righe rimanenti vengono divise in sottotabelle per venditore.

3
HAVING COUNT(*) >= 2

Vengono incluse solo le sottotabelle con almeno 2 righe valide.

📤 Risultato finale (output della Query):

Risultato della query: vendite e totale per i venditori con almeno 2 vendite sopra i 50€
venditore vendite totale
Mario51548.00
Luigi4383.00
Anna2175.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.

✍️ SINTASSI
(Come lo scrivi)
SELECT
→
FROM
→
WHERE
→
GROUP BY
→
HAVING
→
ORDER BY
⚙️ LOGICA
(Come viene eseguito)
FROM
→
WHERE
→
GROUP BY
→
HAVING
→
SELECT
→
ORDER BY

🧐 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

SELECT categoria, SUM(importo)
Esegue per 5° Calcola i valori da mostrare
FROM vendite
Esegue per 1° Trova la tabella
WHERE quantita > 1
Esegue per 2° Filtra le righe inutili
GROUP BY categoria
Esegue per 3° Crea le sottotabelle
HAVING SUM(importo) > 1000
Esegue per 4° Filtra le sottotabelle
ORDER BY totale DESC
Esegue per 6° Ordina il risultato finale
LIMIT 5;
Esegue per 7° Taglia le righe in eccesso

🎯 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):

Risultato della query: vendite, totale e media dei venditori con più di 250€ di venduto
venditore vendite totale media
Mario81662.00207.75
Luigi7473.0067.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):

Risultato della query: totale e pezzi per ogni combinazione venditore-categoria con totale superiore a 100€
venditore categoria totale pezzi
MarioElettronica1348.004
MarioCasa245.007
LuigiCasa185.0010
LuigiAbbigliamento144.006
LuigiElettronica144.007
AnnaAbbigliamento120.002

⚠️ 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;

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.