DataTableの集計を行う(.NET 1.1)
DataTable.Computeメソッドを利用すればDataTable内で集計が行えるらしい。
DataTable.Compute メソッド(System.Data)
とりあえずSUMを行うサンプルを書いてみた。
.Net framework2.0以降だと、Mergeとか便利なメソッドが使えるのでまた違った書き方になるだろう。
''' ----------------------------------------------------------------------------- ''' <summary> ''' DataTableに対して集計を行う ''' </summary> ''' <param name="dtOriginal">集計対象となるテーブル</param> ''' <param name="groupBy">Group By指定するカラム名</param> ''' <returns>集計済テーブル</returns> ''' <remarks> ''' 引数:dtOriginalに存在する列のうち、groupByで指定されなかった列は全てSummaryする。 ''' 数値型でない項目はgroupByで指定しないとDataExceptionが発生する。 ''' </remarks> ''' ----------------------------------------------------------------------------- Private Function DataTableSummary(ByVal dtOriginal As DataTable, ByVal ParamArray groupBy() As String) As DataTable ' 引数チェック For Each key As String In groupBy If (Not dtOriginal.Columns.Contains(key)) Then Throw New Exception("groupByで指定されたカラムがdtOriginalに存在しません") End If Next ' 返却用テーブル。引数で渡されたテーブルのクローンを作成しておく。 Dim dtFilter As DataTable = dtOriginal.Clone() ' 引数:groupByで指定された列を返却用テーブルのキーとして設定する ' MEMO : ArrayList.ToArray()の結果をDataColumn配列にキャストしたものをPrimaryKeyに設定するとなぜか実行時エラー Dim colKeys(groupBy.Length) As DataColumn For i As Integer = 0 To groupBy.Length - 1 colKeys(i) = dtFilter.Columns(groupBy(i)) Next dtFilter.PrimaryKey = colKeys ' テーブルの集計を行う ' DataViewを利用して、処理済みのキーを持つ行を排除しながらループする While dtOriginal.DefaultView.Count > 0 Dim drv As DataRowView = dtOriginal.DefaultView(0) Dim filter As New System.Text.StringBuilder For Each key As String In groupBy ' フィルタ用の文字列を作成する ' KeyColumn1='VALUE1' AND KeyColumn2='VALUE2'……のような文字列が生成される If (filter.Length > 0) Then filter.Append(" AND ") End If filter.Append(String.Format("{0}='{1}'", key, drv.Row(key))) Next Dim newRow As DataRow = dtFilter.NewRow For Each col As DataColumn In dtFilter.Columns If (Array.IndexOf(colKeys, col) >= 0) Then ' groupByで指定された列は引数で渡されたテーブルのデータをそのまま使う newRow(col.ColumnName) = drv.Row(col.ColumnName) Else ' groupByで指定されなかった列はフィルタ文字列を使用してSUM ' HACK : 数値型でないとExceptionが発生してしまう ' そもそもSUM用メソッドなので、集計できない項目をGroupBy指定しないのはおかしいが。 newRow(col.ColumnName) = dtOriginal.Compute(String.Format("SUM({0})", col.ColumnName), filter.ToString()) End If Next ' 集計した行を返却用テーブルに追加 dtFilter.Rows.Add(newRow) ' 集計済のキーを排除するフィルタを追加 If (dtOriginal.DefaultView.RowFilter.Length > 0) Then dtOriginal.DefaultView.RowFilter += String.Format(" AND (Not ({0}))", filter.ToString()) Else dtOriginal.DefaultView.RowFilter += String.Format("(Not ({0}))", filter.ToString()) End If End While dtFilter.AcceptChanges() Return dtFilter End Function
引数を工夫すれば他の集計関数(AVEとかMINとか)にも対応させられる気がする。
速度はイマイチ。