【问题标题】:Determining duplicates in a datatable确定数据表中的重复项
【发布时间】:2012-05-29 05:44:26
【问题描述】:

我有一个从 CSV 文件加载的数据表。我需要根据数据表中的两列(product_idowner_org_id)确定哪些行是重复的。一旦我确定了这一点,我就可以使用该信息来构建我的结果,这是一个仅包含非唯一行的数据表,以及一个仅包含唯一行的数据表。

我查看了此处的其他示例,到目前为止我提出的代码确实可以编译和执行,但似乎认为数据中的每一行都是唯一的。实际上,在测试数据中有 13 行,只有 6 行是唯一的。很明显我做错了什么。

编辑:我想我应该注意,应该ALL删除具有重复项的行,而不仅仅是该行的重复项。例如,如果有 4 个重复项,则应删除所有 4 个而不是 3 个,从 4 个中保留一个唯一行。

EDIT2:或者,如果我可以选择所有重复的行(而不是尝试选择唯一的行),这对我来说很好。无论哪种方式都可以让我得到最终结果。

处理方法中的代码:

MyRowComparer myrc = new MyRowComparer();
var uniquerows = dtCSV.AsEnumerable().Distinct(myrc);

以及以下内容:

public class MyRowComparer : IEqualityComparer<DataRow>
{
    public bool Equals(DataRow x, DataRow y)
    {
        //return ((string.Compare(x.Field<string>("PRODUCT_ID"),   y.Field<string>("PRODUCT_ID"),   true)) ==
        //        (string.Compare(x.Field<string>("OWNER_ORG_ID"), y.Field<string>("OWNER_ORG_ID"), true)));
        return
            x.ItemArray.Except(new object[] { x[x.Table.Columns["PRODUCT_ID"].ColumnName] }) ==
            y.ItemArray.Except(new object[] { y[y.Table.Columns["PRODUCT_ID"].ColumnName] }) &&
            x.ItemArray.Except(new object[] { x[x.Table.Columns["OWNER_ORG_ID"].ColumnName] }) ==
            y.ItemArray.Except(new object[] { y[y.Table.Columns["OWNER_ORG_ID"].ColumnName] });
    }

    public int GetHashCode(DataRow obj)
    {
        int y = int.Parse(obj.Field<string>("PRODUCT_ID"));
        int z = int.Parse(obj.Field<string>("OWNER_ORG_ID"));
        int c = y ^ z;
        return c;
    }
}

【问题讨论】:

  • 我不明白你为什么使用Except - 你为什么不只是比较重要的两列的值?此外,x.Table.Columns["PRODUCT_ID"].ColumnName 的定义应与“PRODUCT_ID”相同,因此您可以跳过列查找。

标签: c# datatable duplicates


【解决方案1】:

您可以使用 LINQ-To-DataSet 和 Enumerable.Except/Intersect:

var tbl1ID = tbl1.AsEnumerable()
        .Select(r => new
        {
            product_id = r.Field<String>("product_id"),
            owner_org_id = r.Field<String>("owner_org_id"),
        });
var tbl2ID = tbl2.AsEnumerable()
        .Select(r => new
        {
            product_id = r.Field<String>("product_id"),
            owner_org_id = r.Field<String>("owner_org_id"),
        });


var unique = tbl1ID.Except(tbl2ID);
var both = tbl1ID.Intersect(tbl2ID);

var tblUnique = (from uniqueRow in unique
                join row in tbl1.AsEnumerable()
                on uniqueRow equals new
                {
                    product_id = row.Field<String>("product_id"),
                    owner_org_id = row.Field<String>("owner_org_id")
                }
                select row).CopyToDataTable();
var tblBoth = (from bothRow in both
              join row in tbl1.AsEnumerable()
              on bothRow equals new
              {
                  product_id = row.Field<String>("product_id"),
                  owner_org_id = row.Field<String>("owner_org_id")
              }
              select row).CopyToDataTable();

编辑:显然我有点误解了你的要求。所以你只有一个DataTable 并且想要获得所有唯一和所有重复的行,这更加直接。您可以将Enumerable.GroupBy 与包含两个字段的匿名类型一起使用:

var groups = tbl1.AsEnumerable()
    .GroupBy(r => new
    {
        product_id = r.Field<String>("product_id"),
        owner_org_id = r.Field<String>("owner_org_id")
    });
var tblUniques = groups
    .Where(grp => grp.Count() == 1)
    .Select(grp => grp.Single())
    .CopyToDataTable();
var tblDuplicates = groups
    .Where(grp => grp.Count() > 1)
    .SelectMany(grp => grp)
    .CopyToDataTable();

【讨论】:

  • 一开始我只有一个数据表,里面有所有的东西。这个例子似乎假设我已经将所有内容分成两个数据集,或者我错过了什么?
  • @user1366062:那我有点误解了你的要求。编辑了我的答案。
  • 完美,这正是我想要的。谢谢!
【解决方案2】:

您的标准已关闭。您正在比较您不感兴趣(Except 排除)的对象集。

相反,尽可能清晰(数据类型)并保持简单:

public bool Equals(DataRow x, DataRow y)
{   
    // Usually you are dealing with INT keys
    return (x["PRODUCT_ID"] as int?) == (y["PRODUCT_ID"] as int?)
      && (x["OWNER_ORG_ID"] as int?) == (y["OWNER_ORG_ID"] as int?);

    // If you really are dealing with strings, this is the equivalent:
    // return (x["PRODUCT_ID"] as string) == (y["PRODUCT_ID"] as string)
    //  && (x["OWNER_ORG_ID"] as string) == (y["OWNER_ORG_ID"] as string)
}  

如果可能,请检查null。也许您想排除相等的行,因为它们的 ID 为空。

观察int?。这不是一个错字。如果您正在处理来自可以是NULL 的列的数据库值,则需要问号。原因是 NULL 值将由 C# 中的 DBNull 类型表示。在这种情况下,使用as 运算符只会给你null(而不是InvalidCastException。 如果您确定,您正在处理INT NOT NULL,使用(int) 进行转换。

字符串也是如此。 (string) 断言您期望非空 DB 值。

编辑1:

类型错误。 ItemArray 不是哈希表。直接使用该行。

编辑2:

添加了string的例子,一些评论

如需更直接的方法,请查看How to select distinct rows in a datatable and store into an array

EDIT3:

关于演员阵容的一些解释。

我建议的另一个链接与您的代码相同。我忘记了您的初衷;-) 我刚刚看到您的代码并响应了最明显的错误,我看到了 - 抱歉

这是我解决问题的方法

using System.Linq;
using System.Data.Linq;

var q = dtCSV
    .AsEnumerable()
    .GroupBy(r => new { ProductId = (int)r["PRODUCT_ID"], OwnerOrgId = (int)r["OWNER_ORG_ID"] })
    .Where(g => g.Count() > 1).SelectMany(g => g);

var duplicateRows = q.ToList();

我不知道这是否 100% 正确,我手头没有 IDE。而且您需要将演员表调整为适当的类型。见我上面的补充。

【讨论】:

  • 这似乎删除了所有重复项,每组重复项除外。例如,如果我有 5 行都匹配 product_id 和 owner_org_id,我会得到 1 行匹配 product_id 和 owner_org_id。我还想删除最后一行。
  • 此外,即使它们是 int,我也无法使用 'as int',因为我在列名上收到错误消息,指出“as 运算符必须与引用类型或可为空的类型一起使用” .我检查了您的链接以获得更直接的方式,这似乎部分有效,但是我无法让它返回整行,而不仅仅是有问题的两列。我需要整行,但只比较两列的重复项。我可能做错了,但这个例子似乎很清楚。
  • @user1366062:我在回答中回复了您的两个 cmets。中间有一段关于演员的部分,最后有一段补充
  • 刚刚看到另一个答案的编辑。将他的代码用于更完整的解决方案。他获取结果行并将它们反馈到DataTable,这可能是您想要的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-22
  • 2011-09-23
  • 2013-01-13
  • 1970-01-01
  • 1970-01-01
  • 2015-09-12
  • 1970-01-01
相关资源
最近更新 更多