【问题标题】:What's The Best Way To Remove Duplicate Rows Based On A Row Item In A DataTable With VB.Net?使用 VB.Net 根据数据表中的行项删除重复行的最佳方法是什么?
【发布时间】:2019-12-05 18:03:59
【问题描述】:

在阅读了互联网上的多篇帖子后,我无法弄清楚如何做到这一点,我只想在下面就这个问题做一个非常清楚的帖子。

如果有人指出我有相同问题对我有帮助的文章,我将删除该帖子。

我在下面有一个示例数据表。我想删除重复的行,但仅在“请求类型”为“取消订单”且“订单号”相同的情况下。

Report Date Time, Order Number, Request Type, Old Value, New Value
12/5/2019 12:00 , TM123456-01 , Cancel Order, 470000000, 5700000000
12/5/2019 12:00 , TM123456-01 , Cancel Order , 123000000, 4560000000
12/5/2019 12:00 , MT123456-02 , Add Order    , 470000000, 5700000000
12/5/2019 12:00 , AP123456-02 , Add Order    , 470000000, 5700000000
12/5/2019 12:00 , ST123456-02 , Remove Order , 470000000, 5700000000

因此,应该从上述数据表中删除的行应该是第 2 行,因为它与第 1 行的“订单号”相同,即“TM123456-01”,其请求类型为“取消订单”。

我想留下的结果是下面的数据表。

Report Date Time, Order Number, Request Type, Old Value, New Value
12/5/2019 12:00 , TM123456-01 , Cancel Order, 470000000, 5700000000
12/5/2019 12:00 , MT123456-02 , Add Order    , 470000000, 5700000000
12/5/2019 12:00 , AP123456-02 , Add Order    , 470000000, 5700000000
12/5/2019 12:00 , ST123456-02 , Remove Order , 470000000, 5700000000

我知道我可以使用嵌套的 For Each 循环来执行此操作,但我想了解如何使用 Microsoft LINQ 或其他方法以更优雅的方式执行此操作。

【问题讨论】:

  • 为什么要删除第二行而不是第一行(我的意思是基于什么考虑)?
  • 它可能是第一个,在我的情况下无关紧要。只是我最终得到了一组关于“订单号”和“请求类型”的不同行,其中请求类型为“取消订单”。
  • 第二行的订单号与第一行相同,即“TM123456-01”,并且它的“请求类型”为“取消订单”,因此所有这些都与行相同1,我希望它从我们的数据表中删除。如果您注意到,旧值和新值是不同的。旧值和新值对数据表中的其他“请求类型”很重要,但对“取消订单”请求类型不重要。希望这是有道理的。

标签: vb.net linq filter datatable unique-values


【解决方案1】:

假设您想从原始表中删除行,而不是创建新表,您可以使用 LINQ 找到要删除的行,然后将其删除。 LINQ 用于查询数据,而不是修改数据。

Dim indicesOfRowsToDelete = dt.AsEnumerable _
                              .Select(Function(r, n) New With { Key r, Key n }) _
                              .GroupBy(Function(rn) New With { Key .OrderNumber = rn.r.Field(Of String)("OrderNumber"), Key .RequestType = rn.r.Field(Of String)("RequestType") }) _
                              .Where(Function(rg) rg.Key.RequestType = "Cancel Order") _
                              .SelectMany(Function(rg) rg.Skip(1).Select(Function(rn) rn.n)) _
                              .OrderByDescending(Function(n) n)

For Each n In indicesOfRowsToDelete
    dt.Rows(n).Delete
Next

这是相同代码的 C# 版本:

var indicesOfRowsToDelete = dt.AsEnumerable()
                              .Select((r, n) => new { r, n })
                              .GroupBy(rn => new { OrderNumber = rn.r.Field<string>("OrderNumber"), RequestType = rn.r.Field<string>("RequestType") })
                              .Where(rg => rg.Key.RequestType == "Cancel Order")
                              .SelectMany(rg => rg.Skip(1).Select(rn => rn.n))
                              .OrderByDescending(n => n);

foreach (var n in indicesOfRowsToDelete)
    dt.Rows[n].Delete();

但是,由于您发布的解决方案创建了一个包含所需行的新表,因此这里是一个 LINQ 查询,用于在 C# 中创建一个新的DataTable

var newDT = dt.AsEnumerable()
              .GroupBy(r => new { OrderNumber = r.Field<string>("OrderNumber"), RequestType = r.Field<string>("RequestType") })
              .SelectMany(rg => rg.Key.RequestType == "Cancel Order"
                                    ? rg.Take(1) // or other selection process
                                    : rg
              )
              .CopyToDataTable();

【讨论】:

  • 非常感谢,谢谢。仅使用 If 和 Else 语句似乎更容易理解。但我会将其标记为答案,而不是我的答案,因为我确实询问了 Linq 是什么。如果你有 C# Linq 并且可以发布它,我想看看它,但如果没有,那也没关系。感谢您的帮助!
  • 我认为 Mark S 仅表示查询,但我知道它如何与删除一词混淆。
  • @MarkS 我添加了 C# 版本,以及用于创建新 DataTable 而不是删除行的 C#。
【解决方案2】:

C#版本:

var result = orders.GroupBy(x => new { x.OrderNumber, x.RequestType})
            .SelectMany(x => x.Key.RequestType=="Cancel Order" ? x.Take(1) : x.ToList());

【讨论】:

  • 这不适用于DataTable,也不会返回DataTable
  • @NetMage,没有注意到对数据表的要求。
【解决方案3】:

我为解决这个问题所做的是一个函数,它传入一个数据表并输出一个包含我想要删除的重复项的数据表。

我使用 For Each 循环和 if 语句删除了重复项。我仍然相信应该有一种方法可以用 Linq 做到这一点。如果您发布答案将不胜感激,但现在,我将在下面发布我的答案。

请注意,DataTables 是 in 和 out 参数,因此它们不会在我的工作流程中声明。

Dim ListOfOrderNumbers As New List(Of String)

ForEach row in DataTable1

    If row.Item("RequestType").ToString = "Cancel Order" Then
        If ListOfOrderNumbers.Contains(row.Item("OrderNumber").ToString) Then
            'Do nothing
        Else
            DataTable2.Rows.Add(row.Item("ReportDateTime"), row.Item("OrderNumber").ToString, row.Item("RequestType").ToString, row.Item("OldValue").ToString, row.Item("NewValue").ToString)
            'Add the row to DataTabe2 since we know the order number is not in it yet.
            ListOfOrderNumbers.Add(row.Item("OrderNumber").ToString)
            'Add the OrderNumber to ListOfOrderNumbers so a row with the same OrderNumber doesn't get added to DataTable2 again.
    Else
        DataTable2.Rows.Add(ReportDateTime, OrderNumber, RequestType, OldValue, NewValue)

【讨论】:

  • 您应该使用HashSet(Of String) 而不是List(Of String),因为主要目的是测试Contains。我还建议row.Field(Of String)("OrderNumber")row.Item("OrderNumber").ToString 更可取,后者也简化为row("OrderNumber").ToString
  • 感谢您的建议。我将阅读哈希集。我从未听说过这种变量类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-25
  • 2010-09-15
  • 1970-01-01
  • 1970-01-01
  • 2016-10-24
相关资源
最近更新 更多