Pivot SQL - trasformare righe in colonne nei report

Emilio Santoro 14 maggio 2026
Tabella pivot che mostra le vendite per prodotto e trimestre, con dati aggregati per anno. L'analisi pivot evidenzia i totali di quantità e incasso.

Indice

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.

Tabella pivot creata da dati SQL. Visualizza conteggi per paese, città e persona, con un totale per riga.

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.

Domande frequenti

Si sceglie la colonna che contiene le categorie, come trimestre, e si applica una funzione di aggregazione come SUM, COUNT o AVG. In questo modo si ottiene, ad esempio, una riga per regione con colonne Q1, Q2, Q3 e Q4.

SQL Server e Oracle offrono una sintassi PIVOT dedicata, mentre CASE con GROUP BY è più portabile e funziona bene anche in MySQL, MariaDB e SQLite. La seconda soluzione è più verbosa, ma consente di combinare più metriche e condizioni nella stessa query.

Quando non esistono righe per una categoria, il risultato del pivot contiene normalmente NULL. È possibile sostituirlo esplicitamente con zero usando COALESCE, ma NULL e zero non hanno lo stesso significato: il primo indica un valore non disponibile, il secondo un valore misurato pari a zero.

Il dynamic SQL è utile quando le categorie, come prodotti, anni o stati degli ordini, cambiano nel tempo e non possono essere elencate in una query statica. I nomi generati devono essere validati, gli input degli utenti non vanno concatenati direttamente e filtri e valori devono essere gestiti con parametri.

È efficace soprattutto per dashboard, esportazioni e report comparativi. Per join, controlli di qualità, procedure ETL e data warehouse è generalmente preferibile mantenere i dati in formato lungo e normalizzato, usando il formato largo vicino al momento della visualizzazione.

Valuta l'articolo

Valutazione: 0.00 Numero di voti: 0

Tag

pivot
crosstab
dynamic sql
sql server
postgresql
Autor Emilio Santoro
Emilio Santoro
Mi chiamo Emilio Santoro e mi occupo di analisi dati, business intelligence e data science da 4 anni. Ho iniziato a esplorare questo mondo perché sono affascinato dal potenziale dei dati nel trasformare le decisioni aziendali e nel fornire una comprensione più profonda dei fenomeni. Sul sito mondobi.it, cerco di rendere accessibili concetti complessi, condividendo la mia esperienza nel collegare la teoria alla pratica e nel presentare informazioni in modo chiaro e organizzato. La mia priorità è offrire contenuti accurati, utili e sempre aggiornati, basati su un'attenta verifica delle fonti e un confronto costante con le tendenze del settore.

Condividi post

Scrivi un commento