使用条件选择和汇总 DataTable 行

Posted

技术标签:

【中文标题】使用条件选择和汇总 DataTable 行【英文标题】:Select and sum DataTable rows with criteria 【发布时间】:2020-11-21 12:21:35 【问题描述】:

我有这个数据表:

DataTable dt = new DataTable();
dt.Columns.Add("BBG IPC code", typeof(double));
dt.Columns.Add("Issuer Group", typeof(string));
dt.Columns.Add("Seniority", typeof(string));
dt.Columns.Add("Nom Value", typeof(double));
dt.Columns.Add("Mkt Value", typeof(double));
dt.Columns.Add("Rating", typeof(string));
dt.Columns.Add("Sector", typeof(string));
dt.Columns.Add("Analyst", typeof(string));
dt.Rows.Add(new object[]  117896, "Financiere", "Senior", 101, 20000.76, "BB", "Materials", "BAETZ" );
dt.Rows.Add(new object[]  117896, "Financiere", "Senior", 356, 300500, "BBB", "Materials", "BAETZ" );
dt.Rows.Add(new object[]  117896, "Financiere", "Senior", 356, 30000, "BBB", "Energy", "BAETZ" );
dt.Rows.Add(new object[]  117896, "Financiere", "Covered", 4888, 10000, "BB", "Energy", "BAETZ" );
dt.Rows.Add(new object[]  117896, "Financiere", "Covered", 645, 50000, "BBB", "Energy", "BAETZ" );
dt.Rows.Add(new object[]  117897, "Scentre Group", "Senior", 46452, 51066.5, "AA", "Energy", "BAETZ" );
dt.Rows.Add(new object[]  117898, "Vereniging Achmea", "Senior", 778, 90789.9, "C", "Insurance", "BAETZ" );
dt.Rows.Add(new object[]  117898, "Vereniging Achmea", "Senior", 7852, 10055.66, "C", "Utilities", "BAETZ" );

对于BBG IPC codeSeniority 的每一对值,我需要检查RatingSector 列的值是否相同,如果相同,则合并这些行并将@ 的值相加987654326@ 和Nom Value。 相反,如果一个或两个不相同,我需要选择具有最高值的行Mkt Value(如果值相等,只需取 1 行)并丢弃其他行但在列 Mkt ValueNom Value 我仍然需要所有行的总和。

例如:对于代码中的 BBG IPC code 数字 117896,RatingSector 的值不同,我需要最高值为 Mkt Value 的行(第二行 300500)并丢弃其他 2 Mkt Value 较低的行,但在丢弃它们之前,我需要将 300500+20000+30000 和 356+356+101 相加。 结果是 117896,"Financiere","Senior",813,350500,"BBB", "Materials", "BAETZ"

我已经尝试过类似的方法,但是有一个错误告诉我不能在 CopyToDataTable 中放入一个引用字段“Seniority”的字符串值...

DataTable maxIPC_Seniority = dt.AsEnumerable()
            .OrderByDescending(x => x.Field<double>("Mkt Value"))
            .GroupBy(x => x.Field<double>("IPC"), x => x.Field<string>("Seniority"))
            .Select(x => x.FirstOrDefault())
            .CopyToDataTable();

仍然是对丢弃的行求和的问题。谢谢你的帮助。

【问题讨论】:

您将"Seniority" 指定为元素选择器,因此您得到的只是string 字段的集合,因此.Select(x =&gt; x.FirstOrDefault()) 选择的是string,而不是@ 987654341@. 【参考方案1】:

一个问题是,当您调用GroupBy 时,您将"IPC" 列设置为Key 选择器,但表中没有"IPC" 列。相反,您应该使用实际的列名"BBG IPC code"

下一个问题是你调用了GroupBy 的重载,它将一个键选择器作为第一个参数,一个元素选择器作为第二个参数,所以它只是选择组中的"Seniority" 列。

相反,要按两列作为键进行分组,我们需要为 Key 创建一个新的匿名对象,其中包含具有列值的属性:

var maxIPC_Seniority = dt.AsEnumerable()
    .OrderByDescending(row => row.Field<double>("Mkt Value"))
    .GroupBy(row =>
        new
        
            IPC = row.Field<double>("BBG IPC code"),
            Seniority = row.Field<string>("Seniority")
        )
    .Select(group => group.FirstOrDefault())
    .CopyToDataTable();

现在,要按照您的意愿组合行,我认为唯一的方法是使用新数据选择 object[] 的集合,然后将它们添加到结果表中,因为我们可以'不只是创建一个没有DataTableDataRow,所以我的回答做了三件事:

    使用所需的列创建一个新的DataTable 从原始表中选择合并的数据作为IEnumerable&lt;object[]&gt; 将每个object[] 作为DataRow 添加到步骤1 中的DataTable

例如:

// Create a new DataTable with the same columns as `dt`
DataTable maxIpcSeniority = dt.Clone();

// Group our set of original data, do the merging of rows as necessary
// and then return the row data as a list of object[]
var maxIpcSeniorityRowData = dt.AsEnumerable()
    .OrderByDescending(row => row.Field<double>("Mkt Value"))
    .GroupBy(row =>
        new
        
            IPC = row.Field<double>("BBG IPC code"),
            Seniority = row.Field<string>("Seniority")
        )
    .Select(group =>
    
        // Since the data is ordered by MktValue already, we can just grab 
        // the first one to use for filling in the non-merged fields
        var firstRow = group.First();

        return new object[]
        
            group.Key.IPC,
            firstRow.Field<string>("Issuer Group"),
            group.Key.Seniority,
            group.Sum(row => row.Field<double>("Nom Value")),
            group.Sum(row => row.Field<double>("Mkt Value")),
            firstRow.Field<string>("Rating"),
            firstRow.Field<string>("Sector"),
            firstRow.Field<string>("Analyst")
        ;
    )
    .ToList();

// Add each set of rowData to our new table
foreach (var rowData in maxIpcSeniorityRowData)

    maxIpcSeniority.Rows.Add(rowData);


如果由于某种原因您不能使用花括号,您可以使用Tuple(甚至创建一个单独的类)来存储GroupBy 字段而不是匿名类型。这样,您可以通过构造函数添加值,而不是在花括号中初始化属性。 (请注意,如果您确实创建了一个类来执行此操作,则需要覆盖 EqualsGetHashCode 才能使分组正常工作)。

这是一个使用 Tuple&lt;double, string&gt; 的示例:

var maxIpcSeniorityRowData = dt.AsEnumerable()
    .OrderByDescending(row => row.Field<double>("Mkt Value"))
    .GroupBy(row => new Tuple<double, string>(
        row.Field<double>("BBG IPC code"), 
        row.Field<string>("Seniority")))
    .Select(group =>
    
        var firstRow = group.First();

        return new object[]
        
            group.Key.Item1,
            firstRow.Field<string>("Issuer Group"),
            group.Key.Item2,
            group.Sum(row => row.Field<double>("Nom Value")),
            group.Sum(row => row.Field<double>("Mkt Value")),
            firstRow.Field<string>("Rating"),
            firstRow.Field<string>("Sector"),
            firstRow.Field<string>("Analyst")
        ;
    )
    .ToList();

【讨论】:

谢谢,它在 VisualStudio 上运行良好,但不幸的是,我必须在一些调用代码中运行此代码,并且由于代码中 lambda 表达式的大括号而无法运行。它可以做同样的事情,但没有他们?例如这不起作用... new IPC = row.Field("BBG IPC code"), Seniority = row.Field("Seniority") ) 我该怎么办? 在这种情况下,您可以使用Tuple(甚至创建一个单独的类)来存储GroupBy 字段而不是匿名类型,并通过构造函数添加值。我更新了示例(在底部)。

以上是关于使用条件选择和汇总 DataTable 行的主要内容,如果未能解决你的问题,请参考以下文章

C# DataTable多条件汇总

根据行条件隐藏 p:dataTable 选择复选框

asp.net c# 如何取出datatable指定行的值

使用多个排序条件对 System.Data.DataTable 进行排序

如何定义一个函数来根据多个条件汇总和选择数据?

在C#中进行DataTable操作:根据列数据插入一些汇总行