VB.NET Eseguire Pivot su DataTable (it-IT)
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: