【问题标题】:Remove duplicates by field from one table using another using LINQ使用 LINQ 使用另一个表从一个表中删除重复项
【发布时间】:2010-02-13 22:46:17
【问题描述】:

我必须在DataTable 中只留下数据库中当前不存在日期的记录。

所以我使用存储过程读取了所有现有日期(是否正确?):

SELECT DISTINCT CAST(S.[date] AS DATE) -- original date is DATETIME2(0)
FROM ...
WHERE ...

并将其加载到DataTable:

var tableDate = new DataTable();
new SqlDataAdapter(command).Fill(tableDate);

现在如何从另一个表中删除所有不必要的行?我认为LINQ 可以提供帮助,但我不确定如何..

【问题讨论】:

  • 我不确定我是否理解这些要求。您的意思是您需要查找数据库中给定表中不存在的所有日期值,还是您的意思是您需要从数据库中的表 A 中删除表 B 中不存在日期值的行?
  • @Thomas:让我描述一下情况。我需要使用 SqlBulCopy 向数据库添加新数据。但首先我需要清理它——我没有添加数据库中已经存在的数据。标准 - 日期。例如,首先我添加了 1 月 1 日、2 日、3 日的数据。之后 - 2,3,4。我需要从第二组中删除 Jan, 2。

标签: .net linq ado.net datatable duplicate-removal


【解决方案1】:

我正在查看您的答案,您说它有效,而您只想知道如何在“单个 LINQ 查询”中做到这一点。请记住,这些查询都有延迟执行,因此以下两个查询在功能上是等效的:

var q =
    from d in dates
    select d.Field<DateTime>("date");
return
    (from r in records
     where !q.Contains(r.Field<DateTime>("date"))
     select r).CopyToDataTable();

还有:

return
    (from r in records
     where !dates
         .Select(d => d.Field<DateTime>("date"))
         .Contains(r.Field<DateTime>("date"))
     select r).CopyToDataTable();

第二个版本更难阅读,但仍然是“一次查询”。


话虽如此,这些示例似乎都与您的问题标题不匹配,这表明您正在尝试删除重复的行。如果这确实是你想要做的,这里有一个方法可以做到这一点:

static DataTable RemoveDuplicates(DataTable dt)
{
    return
        (from row in dt.Rows.OfType<DataRow>()
         group row by row.Field<string>("date") into g
         select g
            .OrderBy(r => r.Field<int>("ID"))
            .First()).CopyToDataTable();
}

如果您不关心删除了 哪些 个重复项,那么您可以删除 OrderBy 行。您可以按如下方式进行测试:

static void Main(string[] args)
{
    using (DataTable original = CreateSampleTable())
    using (DataTable filtered = RemoveDuplicates(original))
    {
        DumpTable(filtered);
    }
    Console.ReadKey();
}

static DataTable CreateSampleTable()
{
    DataTable dt = new DataTable();
    dt.Columns.Add("ID", typeof(int));
    dt.Columns.Add("Code", typeof(string));
    dt.Columns.Add("Name", typeof(string));
    dt.Rows.Add(1, "123", "Alice");
    dt.Rows.Add(2, "456", "Bob");
    dt.Rows.Add(3, "456", "Chris");
    dt.Rows.Add(4, "789", "Dave");
    dt.Rows.Add(5, "123", "Elen");
    dt.Rows.Add(6, "123", "Frank");
    return dt;
}

static void DumpTable(DataTable dt)
{
    foreach (DataRow row in dt.Rows)
    {
        Console.WriteLine("{0},{1},{2}",
            row.Field<int>("ID"),
            row.Field<string>("Code"),
            row.Field<string>("Name"));
    }
}

(在此示例中,只需将RemoveDuplicates 方法中的“日期”替换为“代码”)

希望其中之一能回答您的问题。否则,我认为您必须更清楚自己的要求。

【讨论】:

    【解决方案2】:

    你可以使用Except()

    return records.Except(dates);

    更新: 如果您的DataTable 有输入字段,那么它应该如下所示:

    var excluded = arbDates.Rows.OfType<System.Data.DataRow>().Select(a => a[0]) .Except(excDates.Rows.OfType<System.Data.DataRow>().Select(e => e[0]));

    否则你可以施放它:

    var excluded = arbDates.Rows.OfType<System.Data.DataRow>() .Select(a => Convert.ToDateTime(a[0].ToString())) .Except( excDates.Rows.OfType<System.Data.DataRow>() .Select(e => Convert.ToDateTime(e[0].ToString())));

    【讨论】:

    • 嗨。您能帮我用 q1.Except(q2) 编写一个 LINQ 查询吗?如何将选择合二为一?
    • 不幸的是,它没有奏效。 table.AsEnumerable().Except(Database.CreateDataTable(command).AsEnumerable()).ToArray() 每次都返回与表最初相同的记录数。我会尝试使用自定义比较器并报告。
    • 我现有的自定义比较器也没有帮助。我在下面的答案中发布了它。有什么想法吗?
    【解决方案3】:

    您的 SQL 语句看起来不错。据我了解,您正在投射以获取从午夜开始的默认时间值。因此,要比较的另一个表中的日期也必须与该格式匹配,以便将日期与中性时间进行比较。如果不是,您仍然可以使用下面的代码,但您必须在引用 tableResult 行字段的任何位置添加 .Date 属性。我也使用了Field&lt;DateTime&gt;(0),但根据您的查询和您之前的示例,您可能需要使用Field&lt;DateTime&gt;("date")

    无需自定义比较器。要将您的 LINQ 查询合并为单个查询,您只需使用 let 关键字并将中间结果传递给查询并引用它。

    试试这个:

    var tableDate = new DataTable();
    new SqlDataAdapter(command).Fill(tableDate);
    
    // this is the other table that has other dates, so populate as needed
    var tableResult = new DataTable();
    
    var newTable =
        (from row in tableResult.AsEnumerable()
        let uniqueRows = tableResult.AsEnumerable().Select(r => r.Field<DateTime>(0))
                                    .Except(tableDate.AsEnumerable().Select(r => r.Field<DateTime>(0)))
        where uniqueRows.Contains(row.Field<DateTime>(0))
        select row).CopyToDataTable();
    

    在点表示法中,查询将是:

    var newTable = tableResult.AsEnumerable()
        .Select(row => new
        {
            Row = row,
            UniqueRows =  tableResult.AsEnumerable()
                                     .Select(r => r.Field<DateTime>(0))
                                     .Except(tableDate.AsEnumerable().Select(r => r.Field<DateTime>(0)))
        })
        .Where(item => item.UniqueRows.Contains(item.Row.Field<DateTime>(0)))
        .Select(item => item.Row)
        .CopyToDataTable();
    

    您可以使用tableResult.Rows.Cast&lt;DataRow&gt;()tableResult.Rows.OfType&lt;DataRow&gt;() 而不是tableResult.AsEnumerable()。所有这些方法的结果都是相同的。

    如果您想从现有表中删除重复项(而不是将其复制到新表中),您可以从表中删除 Intersect method 返回的项目:

    var commonDates = tableDate.AsEnumerable().Select(row => row.Field<DateTime>(0))
                               .Intersect(tableResult.AsEnumerable().Select(row => row.Field<DateTime>(0)));
    
    for (int index = tableResult.Rows.Count - 1; index >= 0; index--)
    {
        if (commonDates.Contains(tableResult.Rows[index].Field<DateTime>(0)))
        {
            tableResult.Rows.RemoveAt(index);
        }
    }
    

    【讨论】:

      【解决方案4】:

      据我了解,您正在尝试对来自某些导入的数据进行重复数据删除。您可能不需要使用 LINQ 执行此操作。尽管帖子标题建议使用 LINQ,但您后来质疑 LINQ 是否可能是最佳解决方案,并且根据我们所知道的,我认为您可以使用单个 Insert 语句来做到这一点。

      首先,我建议将数据批量复制到数据库中的临时位置(如果您还没有这样做),如下所示:

      Create Table TempBulkCopyData
      (
          Id int not null identity(1,1)
          , Date DateTime2 not null
          , ...
      )
      

      批量复制到临时位置的优点之一是您可以添加索引等以加快清理过程。要对数据进行重复数据删除,您可以运行如下查询:

      Insert DestinationData(...)
      Select ...
      From BulkCopyData As BCD
      Where Id = (
                  Select Min(BCD2.[Id])
                  From BulkCopyData As BCD2
                  Where Cast(BCD2.[Date] As Date) = Cast(BCD.[Date] As Date)
                  )
      

      或者

      Insert DestinationData(...)
      Select ...
      From BulkCopyData As BCD
      Where Id = (
                  Select Min(BCD2.[Id])
                  From BulkCopyData As BCD2
                  Where DateDiff(d, BCD.[Date], BCD2.[Date]) = 0
                  )
      

      这将提取它找到的第一个日期(ID 最低的那个)。这显然有些武断,但为了更精确,我们需要更多地了解数据结构和要求。

      【讨论】:

        猜你喜欢
        • 2011-05-17
        • 2010-11-29
        • 2014-01-15
        • 2015-12-03
        • 1970-01-01
        • 2011-10-13
        • 1970-01-01
        • 2020-03-16
        • 1970-01-01
        相关资源
        最近更新 更多