无法基于两列旋转 C# 数据表

Posted

技术标签:

【中文标题】无法基于两列旋转 C# 数据表【英文标题】:Unable to pivot C# datatable based on two columns 【发布时间】:2021-06-23 21:55:08 【问题描述】:

我有一个包含三列的 C# 数据表。表格是这样的:

DataTable dt = new DataTable();
            dt.Columns.Add("Name", typeof(string));
            dt.Columns.Add("Type", typeof(string));
            dt.Columns.Add("Val", typeof(string));


             dt.Rows.Add("One", "NZ", "100");
        dt.Rows.Add("One", "EN", "200");
        dt.Rows.Add("One", "CA", "300");
        dt.Rows.Add("Two", "ZM", "400");
        dt.Rows.Add("Two", "SA", "200");
        dt.Rows.Add("Two", "NZ", "440");
        dt.Rows.Add("Two", "EN", "990");
        dt.Rows.Add("Three", "IN", "660");
        dt.Rows.Add("Three", "CH", "994");
        dt.Rows.Add("Three", "JP", "900");
        dt.Rows.Add("Three", "CA", "50");
        dt.Rows.Add("Four", "WI", "330");

旋转后的预期输出:

           NZ     EN    CA       ZM      SA       IN       CH     JP      WI
One        100    200   300
Two        440    990            400     200
Three                   50                        660       994    900 
Four                                                                       330

我想旋转 Type 列,但基于 Name 列。

我正在尝试使用以下代码对其进行旋转并出现错误:

public static DataTable PivotDataTable(DataTable table, string columnX,
                                                    params string[] columnsToIgnore)
        
            //Create a DataTable to Return
            DataTable returnTable = new DataTable();

            if (columnX == "")
                columnX = table.Columns[0].ColumnName;

            //Add a Column at the beginning of the table

            returnTable.Columns.Add(columnX);

            //Read all DISTINCT values from columnX Column in the provided DataTale
            List<string> columnXValues = new List<string>();

            //Creates list of columns to ignore
            List<string> listColumnsToIgnore = new List<string>();
            if (columnsToIgnore.Length > 0)
                listColumnsToIgnore.AddRange(columnsToIgnore);

            if (!listColumnsToIgnore.Contains(columnX))
                listColumnsToIgnore.Add(columnX);

            foreach (DataRow dr in table.Rows)
            
                string columnXTemp = dr[columnX].ToString();
                //Verify if the value was already listed
                if (!columnXValues.Contains(columnXTemp))
                
                    //if the value id different from others provided, add to the list of 
                    //values and creates a new Column with its value.
                    columnXValues.Add(columnXTemp);
                    returnTable.Columns.Add(columnXTemp);
                
                else
                
                    //Throw exception for a repeated value
                    throw new Exception("The inversion used must have " +
                                        "unique values for column " + columnX);
                
            

            //Add a line for each column of the DataTable

            foreach (DataColumn dc in table.Columns)
            
                if (!columnXValues.Contains(dc.ColumnName) &&
                    !listColumnsToIgnore.Contains(dc.ColumnName))
                
                    DataRow dr = returnTable.NewRow();
                    dr[0] = dc.ColumnName;
                    returnTable.Rows.Add(dr);
                
            

            //Complete the datatable with the values
            for (int i = 0; i < returnTable.Rows.Count; i++)
            
                for (int j = 1; j < returnTable.Columns.Count; j++)
                
                    returnTable.Rows[i][j] =
                      table.Rows[j - 1][returnTable.Rows[i][0].ToString()].ToString();
                
            

            return returnTable;
        

我得到的错误:'所使用的反转必须具有列类型的唯一值'

我该如何解决这个错误?

【问题讨论】:

是的,异常就在您自己的代码中。为避免这种情况,columnXValues.Contains(columnXTemp) 必须始终评估为 false 所以要么使用不同的条件,要么使用满足您已经编写的条件的数据。 【参考方案1】:

使用此扩展方法:

// pivot a DataTable to a new DataTable
// By field is row id and grouping key
// Over field creates new column names
// Value field is value for new columns
public static DataTable PivotByOverWith(this DataTable dt, string ByRowFieldName, string OverColFieldName, string WithValueFieldName) 
    var res = new DataTable();
    if (dt.Rows.Count > 0) 
        var dtg = dt.AsEnumerable().GroupBy(r => r[ByRowFieldName], r => new  Over = r[OverColFieldName].ToString(), With = r[WithValueFieldName] );

        var hasByRow = ByRowFieldName != OverColFieldName;
        if (hasByRow)
            res.Columns.Add(new DataColumn(ByRowFieldName, dt.Columns[ByRowFieldName].DataType));

        var valueDataType = dt.Columns[WithValueFieldName].DataType;
        var colNames = dtg.SelectMany(rg => rg.Select(r => r.Over)).Distinct();
        foreach (var n in colNames)
            res.Columns.Add(n, valueDataType);

        foreach (var rg in dtg) 
            var newr = res.NewRow();
            if (hasByRow)
                newr[ByRowFieldName] = rg.Key;
            foreach (var r in rg)
                newr[r.Over] = r.With;
            res.Rows.Add(newr);
        
    
    return res;

你可以计算你的答案:

var ans = dt.PivotByOverWith("Name", "Type", "Val");

【讨论】:

【参考方案2】:

这就是我之前做过数百次的方式:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;

namespace ConsoleApplication1

    class Program
    
        static void Main(string[] args)
        
            DataTable dt = new DataTable();
            dt.Columns.Add("Name", typeof(string));
            dt.Columns.Add("Type", typeof(string));
            dt.Columns.Add("Val", typeof(int));


            dt.Rows.Add("One", "NZ", 100);
            dt.Rows.Add("One", "EN", 200);
            dt.Rows.Add("One", "CA", 300);
            dt.Rows.Add("Two", "ZM", 400);
            dt.Rows.Add("Two", "SA", 200);
            dt.Rows.Add("Two", "NZ", 440);
            dt.Rows.Add("Two", "EN", 990);
            dt.Rows.Add("Three", "IN", 660);
            dt.Rows.Add("Three", "CH", 994);
            dt.Rows.Add("Three", "JP", 900);
            dt.Rows.Add("Three", "CA", 50);
            dt.Rows.Add("Four", "WI", 330);

            string[] types = dt.AsEnumerable().Select(x => x.Field<string>("Type")).Distinct().OrderBy(x => x).ToArray();

            DataTable pivot = new DataTable();
            pivot.Columns.Add("Name");
            foreach (string type in types)
            
                pivot.Columns.Add(type, typeof(int));
            

            var groups = dt.AsEnumerable().GroupBy(x => x.Field<string>("Name"));

            foreach (var group in groups)
            
                DataRow newRow = pivot.Rows.Add();
                newRow["Name"] = group.Key;
                foreach (DataRow row in group)
                
                    newRow[row.Field<string>("Type")] = row.Field<int>("Val");
                
            

        
    

【讨论】:

以上是关于无法基于两列旋转 C# 数据表的主要内容,如果未能解决你的问题,请参考以下文章

如何基于两列进行透视

基于两列或多列的 Spark DataFrame 聚合

C# Winform DataTable 怎么过滤两列不重复

如何将数据库中的列字段添加到字符串生成器 c# 并实现两列布局

基于两列 SQL 搜索数据

如何基于两列组合两个数据框? [复制]