Quando i dati sono registrati riga per riga, una semplice tabella può diventare difficile da leggere. La tecnica del pivot SQL permette di trasformare categorie presenti nelle righe in colonne, così da ottenere report più chiari per vendite, budget, KPI e analisi temporali. Mostro come funziona, quali query usare nei principali database e quali errori evitare.
La trasformazione che rende i dati più leggibili
- PIVOT converte i valori di una colonna in nuove colonne.
- Serve quasi sempre una funzione di aggregazione come SUM, COUNT o AVG.
- SQL Server e Oracle hanno una sintassi PIVOT dedicata, mentre PostgreSQL usa spesso crosstab.
- La combinazione CASE + GROUP BY è più portabile tra database diversi.
- Le colonne dinamiche sono comode, ma possono complicare sicurezza, manutenzione e prestazioni.

Dalle righe alle colonne senza perdere il significato dei dati
Immaginiamo una tabella chiamata vendite con quattro campi: regione, anno, trimestre e importo. Il formato normale contiene una riga per ogni combinazione di regione e trimestre, mentre un report direzionale potrebbe richiedere una colonna per ogni trimestre.
| Regione | Trimestre | Importo |
|---|---|---|
| Nord | Q1 | 120000 |
| Nord | Q2 | 135000 |
| Centro | Q1 | 98000 |
Il risultato desiderato sarebbe una riga per regione con le colonne Q1, Q2, Q3 e Q4. Il pivot non cambia il contenuto originale del database: modifica soltanto la forma del risultato restituito dalla query.
La parte più importante è capire quale campo deve diventare una colonna e quale valore deve essere aggregato. Se esistono più righe per la stessa regione e lo stesso trimestre, il database deve sapere se sommare gli importi, contarli o calcolarne la media.
Come usare PIVOT in SQL Server
In SQL Server l’operatore PIVOT è adatto quando le categorie da trasformare sono note in anticipo. Per ottenere il totale trimestrale del 2025, si può scrivere:
SELECT
regione,
[Q1],
[Q2],
[Q3],
[Q4]
FROM
(
SELECT
regione,
trimestre,
importo
FROM vendite
WHERE anno = 2025
) AS dati
PIVOT
(
SUM(importo)
FOR trimestre IN ([Q1], [Q2], [Q3], [Q4])
) AS risultato
ORDER BY regione;La query prima filtra i dati, poi indica che trimestre deve diventare l’asse delle colonne e infine applica SUM(importo). La colonna regione, che non viene usata nel pivot, diventa automaticamente il criterio di raggruppamento.
Se una regione non ha vendite in un determinato trimestre, il risultato normalmente contiene NULL. Per i report economici spesso è più utile mostrare zero, ma la sostituzione va fatta esplicitamente:
SELECT
regione,
COALESCE([Q1], 0) AS Q1,
COALESCE([Q2], 0) AS Q2,
COALESCE([Q3], 0) AS Q3,
COALESCE([Q4], 0) AS Q4
FROM
(
SELECT regione, trimestre, importo
FROM vendite
WHERE anno = 2025
) AS dati
PIVOT
(
SUM(importo)
FOR trimestre IN ([Q1], [Q2], [Q3], [Q4])
) AS risultato;Nel mio lavoro considero NULL e zero due informazioni diverse. NULL significa che non esiste un valore disponibile, mentre zero indica che il valore è stato misurato ed è pari a zero. Confonderli può alterare percentuali, medie e indicatori di andamento.
La soluzione più portabile con CASE e GROUP BY
Non tutti i database offrono un operatore PIVOT con la stessa sintassi. La combinazione di CASE e GROUP BY è spesso la scelta più semplice quando il codice deve funzionare in ambienti diversi, come MySQL, MariaDB o SQLite.
SELECT
regione,
SUM(CASE WHEN trimestre = 'Q1' THEN importo ELSE 0 END) AS Q1,
SUM(CASE WHEN trimestre = 'Q2' THEN importo ELSE 0 END) AS Q2,
SUM(CASE WHEN trimestre = 'Q3' THEN importo ELSE 0 END) AS Q3,
SUM(CASE WHEN trimestre = 'Q4' THEN importo ELSE 0 END) AS Q4
FROM vendite
WHERE anno = 2025
GROUP BY regione
ORDER BY regione;Il meccanismo è diretto: per ogni riga, il CASE assegna l’importo alla colonna corretta e assegna zero alle altre. La funzione SUM raccoglie poi i valori per regione.
Questa variante è più lunga, ma offre un controllo molto preciso. Posso aggiungere condizioni, filtri diversi per ogni colonna oppure metriche multiple, come totale vendite, numero di ordini e margine nello stesso risultato.
SELECT
regione,
SUM(CASE WHEN trimestre = 'Q1' THEN importo ELSE 0 END) AS vendite_Q1,
COUNT(CASE WHEN trimestre = 'Q1' THEN 1 END) AS ordini_Q1,
AVG(CASE WHEN trimestre = 'Q1' THEN importo END) AS media_Q1
FROM vendite
WHERE anno = 2025
GROUP BY regione;Qui emerge un vantaggio concreto rispetto a una sintassi PIVOT rigida: posso creare più indicatori per la stessa categoria. Il compromesso è una query più verbosa, che richiede maggiore attenzione quando le categorie diventano numerose.
Le differenze tra SQL Server, Oracle e PostgreSQL
La logica è comune, ma il comando cambia in base al motore utilizzato. Prima di copiare una query trovata online, verifico sempre il database e la sua versione: un esempio corretto in SQL Server può produrre un errore in PostgreSQL.
| Database | Approccio tipico | Nota pratica |
|---|---|---|
| SQL Server |
PIVOT e UNPIVOT
|
Le colonne da generare sono indicate nell’elenco IN. |
| Oracle |
PIVOT nella clausola FROM
|
Supporta anche la definizione di alias per le categorie. |
| PostgreSQL |
crosstab con l’estensione tablefunc
|
È necessario dichiarare il tipo e il numero delle colonne risultanti. |
| MySQL e SQLite |
CASE con funzioni aggregate |
La soluzione condizionale è generalmente la più immediata. |
In Oracle, una trasformazione equivalente può essere espressa così:
SELECT *
FROM
(
SELECT regione, trimestre, importo
FROM vendite
WHERE anno = 2025
)
PIVOT
(
SUM(importo)
FOR trimestre IN
('Q1' AS Q1, 'Q2' AS Q2, 'Q3' AS Q3, 'Q4' AS Q4)
);In PostgreSQL la funzione più vicina è crosstab, disponibile tramite il modulo tablefunc. Richiede però una struttura più esplicita, con l’ordine delle categorie e la dichiarazione delle colonne di output. Per report occasionali, preferisco spesso usare CASE e GROUP BY, perché il codice è più leggibile per il team.
Colonne dinamiche, valori mancanti e prestazioni
Il problema più frequente nasce quando le categorie non sono fisse. I trimestri sono prevedibili, ma prodotti, anni, stati ordine o campagne marketing possono cambiare nel tempo. In questi casi una query statica non mostra automaticamente la nuova categoria.
La soluzione è il dynamic SQL, cioè la costruzione della query in base ai valori presenti nei dati. In SQL Server si può generare l’elenco delle colonne e passarlo a sp_executesql, mentre in altri ambienti si ricorre a procedure o codice applicativo.
- Valida sempre i nomi delle colonne generati.
- Non concatenare direttamente input forniti dagli utenti.
- Usa parametri per filtri e valori, così da ridurre il rischio di SQL injection.
- Definisci un ordine stabile per le colonne.
- Controlla cosa accade quando una categoria non contiene righe.
La flessibilità ha un costo. Un report con centinaia di colonne è difficile da leggere, può aumentare il lavoro del database e spesso è meno utile di un risultato normale affidato poi a Power BI, Excel o a un altro strumento di visualizzazione.
Per le prestazioni, parto sempre da una query aggregata e filtrata prima della rotazione. Gli indici sui campi usati in WHERE, JOIN e GROUP BY possono fare una differenza significativa, mentre l’applicazione ripetuta di PIVOT nella stessa istruzione può appesantire l’esecuzione.
Un altro errore comune è usare il pivot per correggere un modello dati progettato male. Se ogni mese viene salvato come una nuova colonna nella tabella, il problema non è la query: è la struttura. Per l’archiviazione preferisco dati in formato lungo, con una riga per periodo, e uso il formato largo soltanto per la presentazione.
Quando conviene ruotare i dati e quando no
Il pivot è efficace per dashboard, esportazioni e report comparativi. Un responsabile può confrontare rapidamente le vendite trimestrali per regione, mentre un analista può mettere a confronto KPI di più reparti nella stessa riga.
Non lo userei invece come formato principale per alimentare trasformazioni successive. Le colonne variabili rendono più complesse le join, i controlli di qualità e le procedure ETL, cioè i processi che estraggono, trasformano e caricano i dati.
| Esigenza | Scelta consigliata |
|---|---|
| Report con categorie fisse | PIVOT o aggregazione condizionale |
| Compatibilità tra più database | CASE e GROUP BY |
| Categorie che cambiano spesso | Dynamic SQL o trasformazione nel livello BI |
| Elaborazioni successive e data warehouse | Formato lungo e normalizzato |
La mia regola è semplice: uso il pivot vicino al punto in cui qualcuno deve leggere o visualizzare il risultato. Mantengo invece il formato lungo nei livelli intermedi, perché è più stabile, più facile da validare e generalmente più adatto all’automazione.
Una scelta tecnica che deve servire alla decisione
Trasformare righe in colonne non è solo un esercizio di sintassi. La scelta corretta dipende dal database, dalla stabilità delle categorie, dal volume dei dati e dall’uso finale del report.
Per iniziare, filtro i dati, aggrego ciò che serve, gestisco esplicitamente NULL e valori mancanti, quindi scelgo tra PIVOT, CASE e GROUP BY oppure uno strumento BI. Così la rotazione resta un aiuto alla lettura e non diventa un ostacolo alla qualità del modello dati.
