Introduzione


In questo articolo vediamo un modo rapido per eseguire l'operazione di pivot su un oggetto di tipo DataTable. Tale oggetto, costituito da una struttura realizzata da colonne e righe, rappresenta un insieme di dati in memoria. L'operazione di pivoting prevede che, a partire da una data matrice, venga realizzato lo scambio tra le righe e le colonne che la costituiscono, sulla base dei valori di riga presenti su una colonna selezionata.

Un esempio di DataTable


Consideriamo una tabella costituita dalle seguenti informazioni (colonne): Nome, Età, Peso, Q.I., e valorizziamola con alcuni dati (righe):
Dim dt As New DataTable("Data")
dt.Columns.Add("Name")
dt.Columns.Add("Age")
dt.Columns.Add("Weight")
dt.Columns.Add("IQ")
 
dt.Rows.Add(New Object() {"Alice", 20, 45, 130})
dt.Rows.Add(New Object() {"Bob", 52, 70, 125})
dt.Rows.Add(New Object() {"Sam", 35, 85, 106})
dt.Rows.Add(New Object() {"Jane", 41, 48, 129})

Si è generata cioè una DataTable, di nome Data, costituita dalle quattro colonne menzionate sopra, e sono state aggiunte altrettante righe, corrispondenti a individui ipotetici. Utilizzando la DataTable dt come DataSource di una DataGridView, otterremo un'esposizione conforme alla definizione:



Eseguire il pivoting rispetto ad una colonna


Supponiamo ora di voler eseguire il pivoting della tabella rispetto alla colonna Name. Desideriamo, cioè, che il contenuto della colonna Name su tutte le righe (Alice, Bob, Sam, Jane) venga trasformato in altrettante colonne, mentre le proprietà Age, Weight, IQ diventeranno le righe da esporre ciascuna nella colonna di appartenenza.

Svolgiamo qui l'operazione in due step: nel primo, scorreremo tutte le righe della tabella originaria, e da essa estrarremo l'elemento di pivoting (nel nostro caso, Name), per andare a generare su una seconda tabella le rispettive colonne. In un secondo passaggio, scorreremo ogni colonna della DataTable sorgente che non sia l'elemento di pivoting, e per ciascuna di esse scorreremo le righe, estraendone i valori e popolando, ad ogni ciclo, righe costituite dai valori di ciascun record sulla colonna analizzata in quel momento. Possiamo cioè scrivere una funzione di questo tipo:

Private Function PivotTable(ByVal dt As DataTable, ByVal ColNum As Integer) As DataTable
    Dim dp As New DataTable("Pivoted")
    dp.Columns.Add("Property")
    For Each row As DataRow In dt.Rows
        dp.Columns.Add(row.Item(ColNum).ToString)
    Next
 
    Dim IColumns As IEnumerable(Of DataColumn) = From c As DataColumn In dt.Columns
                                                 Where c.Ordinal <> ColNum
 
    For Each col As DataColumn In IColumns
        Dim tr(dt.Rows.Count) As Object
        tr(0) = col.ColumnName
 
        Dim i As Integer = 1
        For Each row As DataRow In dt.Rows
            tr(i) = row.Item(col.Ordinal)
            i += 1
        Next
 
        dp.Rows.Add(tr)
    Next
 
    Return dp
End Function

Si noti che la funzione accetta due parametri: il primo è riferito alla DataTable di origine, mentre il secondo è la posizione cardinale della colonna rappresentante l'elemento di pivoting. Supponendo di voler popolare due DataGridView, rispettivamente con i dati di origine e con quelli pivotati sulla colonna Name, potremo rifarci a questo snippet:

Public Class Form1
 
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim dt As New DataTable("Data")
        dt.Columns.Add("Name")
        dt.Columns.Add("Age")
        dt.Columns.Add("Weight")
        dt.Columns.Add("IQ")
 
        dt.Rows.Add(New Object() {"Alice", 20, 45, 130})
        dt.Rows.Add(New Object() {"Bob", 52, 70, 125})
        dt.Rows.Add(New Object() {"Sam", 35, 85, 106})
        dt.Rows.Add(New Object() {"Jane", 41, 48, 129})
 
        DGV_Source.DataSource = dt
        DGV_Dest.DataSource = PivotTable(dt, 0)
    End Sub
 
    Private Function PivotTable(ByVal dt As DataTable, ByVal ColNum As Integer) As DataTable
        Dim dp As New DataTable("Pivoted")
        dp.Columns.Add("Property")
        For Each row As DataRow In dt.Rows
            dp.Columns.Add(row.Item(ColNum).ToString)
        Next
 
        Dim IColumns As IEnumerable(Of DataColumn) = From c As DataColumn In dt.Columns
                                                     Where c.Ordinal <> ColNum
 
        For Each col As DataColumn In IColumns
            Dim tr(dt.Rows.Count) As Object
            tr(0) = col.ColumnName
 
            Dim i As Integer = 1
            For Each row As DataRow In dt.Rows
                tr(i) = row.Item(col.Ordinal)
                i += 1
            Next
 
            dp.Rows.Add(tr)
        Next
 
        Return dp
    End Function
End Class

Nell'esempio si presuppone ovviamente di avere due DataGridView di nome DGV_Source e DGV_Dest, e che il Form che le contiene abbia nome Form1.
Il risultato dell'esecuzione dello snippet è quello di figura: