Click here to Skip to main content
15,895,746 members
Articles / Web Development / ASP.NET

Pivoting DataTable Simplified

Rate me:
Please Sign up or sign in to vote.
4.92/5 (131 votes)
20 Aug 2014CPOL5 min read 430.2K   28.6K   325  
A class to pivot a DataTable with various aggregate functions
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;


// Written by Anurag Gandhi.
// Url: http://www.gandhisoft.com
// Contact me at: soft.gandhi@gmail.com

/// <summary>
/// Pivots the data
/// </summary>
public class Pivot
{
    private DataTable _SourceTable = new DataTable();
    private IEnumerable<DataRow> _Source = new List<DataRow>();

	public Pivot(DataTable SourceTable)
	{
        _SourceTable = SourceTable;
        _Source = SourceTable.Rows.Cast<DataRow>();
	}

    /// <summary>
    /// Pivots the DataTable based on provided RowField, DataField, Aggregate Function and ColumnFields.//
    /// </summary>
    /// <param name="rowField">The column name of the Source Table which you want to spread into rows</param>
    /// <param name="dataField">The column name of the Source Table which you want to spread into Data Part</param>
    /// <param name="aggregate">The Aggregate function which you want to apply in case matching data found more than once</param>
    /// <param name="columnFields">The List of column names which you want to spread as columns</param>
    /// <returns>A DataTable containing the Pivoted Data</returns>
    public DataTable PivotData(string rowField, string dataField, AggregateFunction aggregate, params string[] columnFields)
    {
        DataTable dt = new DataTable();
        string Separator = ".";
        List<string> rowList = _Source.Select(x => x[rowField].ToString()).Distinct().ToList();
        // Gets the list of columns .(dot) separated.
        var colList = _Source.Select(x =>(columnFields.Select(n => x[n]).Aggregate((a, b) => a += Separator + b.ToString())).ToString()).Distinct().OrderBy(m => m);
        
        dt.Columns.Add(rowField);
        foreach (var colName in colList)
            dt.Columns.Add(colName);  // Cretes the result columns.//

        foreach (string rowName in rowList)
        {
            DataRow row = dt.NewRow();
            row[rowField] = rowName;
            foreach (string colName in colList)
            {
                string strFilter = rowField + " = '" + rowName + "'";
                string[] strColValues = colName.Split(Separator.ToCharArray(), StringSplitOptions.None);
                for (int i = 0; i < columnFields.Length; i++)
                    strFilter += " and " + columnFields[i] + " = '" + strColValues[i] + "'";
                row[colName] = GetData(strFilter, dataField, aggregate);
            }
            dt.Rows.Add(row);
        }
        return dt;
    }

    public DataTable PivotData(string rowField, string dataField, AggregateFunction aggregate, bool showSubTotal, params string[] columnFields)
    {
        DataTable dt = new DataTable();
        string Separator = ".";
        List<string> rowList = _Source.Select(x => x[rowField].ToString()).Distinct().ToList();
        // Gets the list of columns .(dot) separated.
        List<string> colList = _Source.Select(x => columnFields.Aggregate((a, b) => x[a].ToString() + Separator + x[b].ToString())).Distinct().OrderBy(m => m).ToList();

        if (showSubTotal && columnFields.Length > 1)
        {
            string totalField = string.Empty;
            for (int i = 0; i < columnFields.Length - 1; i++)
                totalField += columnFields[i] + "(Total)" + Separator;
            List<string> totalList = _Source.Select(x => totalField + x[columnFields.Last()].ToString()).Distinct().OrderBy(m => m).ToList();
            colList.InsertRange(0, totalList);
        }

        dt.Columns.Add(rowField);
        colList.ForEach(x => dt.Columns.Add(x));

        foreach (string rowName in rowList)
        {
            DataRow row = dt.NewRow();
            row[rowField] = rowName;
            foreach (string colName in colList)
            {
                string filter = rowField + " = '" + rowName + "'";
                string[] colValues = colName.Split(Separator.ToCharArray(), StringSplitOptions.None);
                for (int i = 0; i < columnFields.Length; i++)
                    if (!colValues[i].Contains("(Total)"))
                        filter += " and " + columnFields[i] + " = '" + colValues[i] + "'";
                row[colName] = GetData(filter, dataField, colName.Contains("(Total)") ? AggregateFunction.Sum : aggregate);
            }
            dt.Rows.Add(row);
        }
        return dt;
    }

    public DataTable PivotData(string DataField, AggregateFunction Aggregate, string[] RowFields, string[] ColumnFields)
    {
        DataTable dt = new DataTable();
        string Separator = ".";
        var RowList = _SourceTable.DefaultView.ToTable(true, RowFields).AsEnumerable().ToList();
        for (int index = RowFields.Count() - 1; index >= 0; index--)
            RowList = RowList.OrderBy(x => x.Field<object>(RowFields[index])).ToList();
        // Gets the list of columns .(dot) separated.
        var ColList = (from x in _SourceTable.AsEnumerable()
                       select new
                       {
                           Name = ColumnFields.Select(n => x.Field<object>(n))
                               .Aggregate((a, b) => a += Separator + b.ToString())
                       })
                           .Distinct()
                           .OrderBy(m => m.Name);

        //dt.Columns.Add(RowFields);
        foreach (string s in RowFields)
            dt.Columns.Add(s);

        foreach (var col in ColList)
            dt.Columns.Add(col.Name.ToString());  // Cretes the result columns.//

        foreach (var RowName in RowList)
        {
            DataRow row = dt.NewRow();
            string strFilter = string.Empty;

            foreach (string Field in RowFields)
            {
                row[Field] = RowName[Field];
                strFilter += " and " + Field + " = '" + RowName[Field].ToString() + "'";
            }
            strFilter = strFilter.Substring(5);
            
            foreach (var col in ColList)
            {
                string filter = strFilter;
                string[] strColValues = col.Name.ToString().Split(Separator.ToCharArray(), StringSplitOptions.None);
                for (int i = 0; i < ColumnFields.Length; i++)
                    filter += " and " + ColumnFields[i] + " = '" + strColValues[i] + "'";
                row[col.Name.ToString()] = GetData(filter, DataField, Aggregate);
            }
            dt.Rows.Add(row);
        }
        return dt;
    }

    /// <summary>
    /// Retrives the data for matching RowField value and ColumnFields values with Aggregate function applied on them.
    /// </summary>
    /// <param name="Filter">DataTable Filter condition as a string</param>
    /// <param name="DataField">The column name which needs to spread out in Data Part of the Pivoted table</param>
    /// <param name="Aggregate">Enumeration to determine which function to apply to aggregate the data</param>
    /// <returns></returns>
    private object GetData(string Filter, string DataField, AggregateFunction Aggregate)
    {
        try
        {
            DataRow[] FilteredRows = _SourceTable.Select(Filter);
            object[] objList = FilteredRows.Select(x => x.Field<object>(DataField)).ToArray();

            switch (Aggregate)
            {
                case AggregateFunction.Average:
                    return GetAverage(objList);
                case AggregateFunction.Count:
                    return objList.Count();
                case AggregateFunction.Exists:
                    return (objList.Count() == 0) ? "False" : "True";
                case AggregateFunction.First:
                    return GetFirst(objList);
                case AggregateFunction.Last:
                    return GetLast(objList);
                case AggregateFunction.Max:
                    return GetMax(objList);
                case AggregateFunction.Min:
                    return GetMin(objList);
                case AggregateFunction.Sum:
                    return GetSum(objList);
                default:
                    return null;
            }
        }
        catch (Exception ex)
        {
            return "#Error";
        }
    }

    private object GetAverage(object[] objList)
    {
        return objList.Count() == 0 ? null : (object)(Convert.ToDecimal(GetSum(objList)) / objList.Count());
    }
    private object GetSum(object[] objList)
    {
        return objList.Count() == 0 ? null : (object)(objList.Aggregate(new decimal(), (x, y) => x += Convert.ToDecimal(y)));
    }
    private object GetFirst(object[] objList)
    {
        return (objList.Count() == 0) ? null : objList.First();
    }
    private object GetLast(object[] objList)
    {
        return (objList.Count() == 0) ? null : objList.Last();
    }
    private object GetMax(object[] objList)
    {
        return (objList.Count() == 0) ? null : objList.Max();
    }
    private object GetMin(object[] objList)
    {
        return (objList.Count() == 0) ? null : objList.Min();
    }
}

public enum AggregateFunction
{
    Count = 1,
    Sum = 2,
    First = 3,
    Last = 4,
    Average = 5,
    Max = 6,
    Min = 7,
    Exists = 8
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
India India
Anurag Gandhi is a Freelance Developer and Consultant, Architect, Blogger, Speaker, and Ex Microsoft Employee. He is passionate about programming.
He is extensively involved in Asp.Net Core, MVC/Web API, Node/Express, Microsoft Azure/Cloud, web application hosting/architecture, Angular, AngularJs, design, and development. His languages of choice are C#, Node/Express, JavaScript, Asp .NET MVC, Asp, C, C++. He is familiar with many other programming languages as well. He mostly works with MS SQL Server as the preferred database and has worked with Redis, MySQL, Oracle, MS Access, etc. also.
He is active in programming communities and loves to share the knowledge with others whenever he gets the time for it.
He is also a passionate chess player.
Linked in Profile: https://in.linkedin.com/in/anuraggandhi
He can be contacted at soft.gandhi@gmail.com

Comments and Discussions