Pivot (crosstab) table in C#

How to create pivot table in C# and display it in DataGrid?

NOTE: it is available version 2.0 of PivotTable


It exists well-know solution for pre-sorted and pre-grouped values.
What if we would like to pivot free set? Sample below will pivot any DataTable and produce new summarized DataTable. You can pivot Array or ArrayList also, first you should convert your Array to DataTable and then call Pivot method. Data is ready to display in grid.

     public DataTable Pivot(
            DataTable src,
            string VerticalColumnName,
            string HorizontalColumnName,
            string ValueColumnName ) 
        {

                DataTable dst = new DataTable();
                if (src == null || src.Rows.Count == 0)
                    return dst;

                // find all distinct names for column and row
                ArrayList ColumnValues  = new ArrayList();
                ArrayList RowValues     = new ArrayList();
                foreach (DataRow dr in src.Rows)
                {
                    // find all column values
                    object column = dr[VerticalColumnName];
                    if (!ColumnValues.Contains(column))
                        ColumnValues.Add(column);

                    //find all row values
                    object row = dr[HorizontalColumnName];
                    if (!RowValues.Contains(row))
                        RowValues.Add(row);
                }

                ColumnValues.Sort();
                RowValues.Sort();

                //create columns
                dst = new DataTable();
                dst.Columns.Add(VerticalColumnName, src.Columns[VerticalColumnName].DataType);
                Type t = src.Columns[ValueColumnName].DataType;
                foreach (object ColumnNameInRow in RowValues) {
                    dst.Columns.Add(ColumnNameInRow.ToString(), t);
                }

                //create destination rows
                foreach (object RowName in ColumnValues) {
                    DataRow NewRow = dst.NewRow();
                    NewRow[VerticalColumnName] = RowName.ToString();
                    dst.Rows.Add(NewRow);
                }

                //fill out pivot table
                foreach (DataRow drSource in src.Rows) {
                    object key = drSource[VerticalColumnName];
                    string ColumnNameInRow = Convert.ToString(drSource[HorizontalColumnName]);
                    int index = ColumnValues.IndexOf(key);
                    dst.Rows[index][ColumnNameInRow] = sum(dst.Rows[index][ColumnNameInRow], drSource[ValueColumnName]);
                }

                return dst;
        }

        dynamic sum(dynamic a, dynamic b) {
            if (a is DBNull && b is DBNull)
                return DBNull.Value;
            else if (a is DBNull && !(b is DBNull))
                return b;
            else if (!(a is DBNull) && b is DBNull)
                return a;
            else
                return a + b;
        }
Proof:
            object DBNULL = DBNull.Value;
            DataTable src = new DataTable();
            DataTable dst = null;

            src.Columns.Add("City", typeof(string));
            src.Columns.Add("Product", typeof(string));
            src.Columns.Add("Value", typeof(float));
            
            src.Rows.Add("City A", "Product 1", 1.2f);
            src.Rows.Add("City C", "Product 2", 0.3f);
            src.Rows.Add("City A", "Product 1", 1.0f);
            src.Rows.Add("City B", "Product 3", 2.2f);
            src.Rows.Add("City B", "Product 1", 1.5f);
            src.Rows.Add("City A", "Product 2", 0.8f);
            src.Rows.Add("City A", "Product 4", 1.1f);

            PivotTable test = new PivotTable();
            dst = test.Pivot(src, "City", "Product", "Value");

            CollectionAssert.AreEqual(new object[] { "City A", 2.2f,    0.8f,   DBNULL, 1.1f   }, dst.Rows[0].ItemArray);
            CollectionAssert.AreEqual(new object[] { "City B", 1.5f,    DBNULL, 2.2f,   DBNULL }, dst.Rows[1].ItemArray);
            CollectionAssert.AreEqual(new object[] { "City C", DBNULL,  0.3f,   DBNULL, DBNULL }, dst.Rows[2].ItemArray);


Enjoy!

7 comments:

Anonymous said...

Hi Ruslan,

thank you very much for your post. I still have one question left:

I can't use the "dynamic"-keyword cause I'm bound to use VS2008.

How can I deal with your "sum"-method? I'm not sure what "return a + b" returns as I have stated that I'm not used to the "dynamic" keyword.

Thanks for your help!

Markus

Ruslan said...

@Markus:

you can rewrite method with object instead of dynamic.
But you should care about type cast!
For example for float or double you can write

object sum2(object a, object b)
{
if (a is DBNull && b is DBNull)
return DBNull.Value;
else if (a is DBNull && !(b is DBNull))
return Convert.ToDouble(b);
else if (!(a is DBNull) && b is DBNull)
return Convert.ToDouble(a);
else
return Convert.ToDouble(a) + Convert.ToDouble(b);
}

Anonymous said...

Thanks very much!

I had problems with the correct cast in the first place...

Markus

Khundmir said...

Thank you John Yu

Anonymous said...

Thank you very much... This helps me a lot...Great article, simple and best

TROPANG PASAWAY said...
This comment has been removed by the author.
TROPANG PASAWAY said...

Hi everyone is it possible to have like this transposition?



City | Product | Value | Product | Value

CIty A | Product 1 | 1.2 | Product 2 | 0.8

HOWTO: Repair Logitech M325 Mouse

FixIt says that you will find single screw under CE label. It isn't always true.