【问题标题】:Delete newer duplicate value based on two columns .NET Core基于两列 .NET Core 删除较新的重复值
【发布时间】:2020-06-02 18:54:53
【问题描述】:

我在 azure 数据库中有巨大的交易表,我们在其中导入包含 +100 万个对象的文件。

public class Transaction
    {
        [Key]
        public int Id { get; set; }
        public int TransactionId { get; set; }
        public DateTime Date { get; set; }
        public decimal Price { get; set; }
        public int UserId { get; set; }
        public string Product { get; set; }
        public int ClientId { get; set; }
        public int Uploaed { get; set; }
        public string UniqueId { get; set; }
        public string Custom1 { get; set; }
        public string Custom2 { get; set; }
        public string Custom3{ get; set; }

    }

导入所有新数据后,我获取所有新事务 ID,并从数据库中获取该客户端的所有事务 ID。

        // ids from import
        string transactionsString = string.Join(",", transactionIdsCsv);
        var result = await   _transactionsDataRepository.GetByTransactionIdsAndClientId(transactionIdsCsv.ToArray(), clientId);
        // ids from repository
        string transactionsDBString = string.Join(",", result.ToList());

        // remove rows in db where duplicate transactions ids and clientId=ClientId

但我正在努力寻找最有效的方法。我想做类似的事情 delete from transactions where transactionId IN (transactionsDBString) and clientId = ClientID 但这会删除两个值,我只想删除新值(并保留旧值)

但这会是一个好方法吗?由于有数百万行,即使获取 var result = await _transactionsDataRepository... 也可能需要很长时间。

【问题讨论】:

    标签: c# .net-core azure-sql-database


    【解决方案1】:

    我只想删除新值(并保留旧值)

    由于您已经知道如何识别要删除的事务 ID,因此您可以删除必要的行,同时保持最新的像这样(您没有提到它,但我假设您使用的是实体框架 - 给定您的使用[Key] 属性 - 如果我错了,请纠正我):

    var transToRemove = dbContext.Transactions
        .Where(t => t.ClientId == clientId && transIds.Contains(t.TransactionId))
        .GroupBy(t => t.TransactionId, t => t) // Group transactions with the same TransactionId
        .SelectMany(
            group => group.OrderBy(t => t.Date) // Order the oldest first
                         .Skip(1) // Skip the oldest (we want to keep it)
        );
    
    dbContext.Transactions.RemoveRange(transToRemove);
    dbContext.SaveChanges();
    

    编辑:包含一个适用于 Dapper 的示例...

    var cn = // Create your DbConnection
    
    // This query should select all transactions you want to delete excluding
    // those with the oldest Date. This is just like 'transToRemove' above
    var selectQuery = @"
    SELECT t1.Id FROM Transactions t1
    INNER JOIN (
        SELECT
            MIN(tInner.Date) AS FirstTransDate,
            tInner.TransactionId,
            tInner.ClientId
        FROM Transactions tInner
        WHERE tInner.ClientId = @clientId
            AND tInner.TransactionId IN @transIds
        GROUP BY tInner.TransactionId, tInner.ClientId
    ) t2 ON t2.ClientId = t1.ClientId AND t2.TransactionId = t1.TransactionId
    WHERE t1.Date != t2.FirstTransDate
    ";
    var idsToDelete = cn.Query<int>(
        selectQuery,
        new { clientId, transIds }).ToList();
    
    // Delete the whole list in one go
    cn.Execute("DELETE FROM Transactions WHERE Id in @idsToDelete", new {idsToDelete});
    

    (灵感来自here和here)

    我没有使用 Dapper 对此进行测试,但根据我制作的 this fiddle,idsToDelete 的列表应该是正确的。需要注意的几点:

    1. 根据您的transIds 列表的长度(我相信在您自己的示例中这些ID 在result 中),您可能希望以较小的批次重复此操作,而不是尝试一次性删除整个列表。
    2. 上面的 SQL 查询没有考虑两个“重复”事务是否具有相同的“最旧”日期。如果这可能发生在您的表中,那么此查询只会删除除这两个之外的所有“重复”行。

    改进

    我认为您应该考虑一些与您的设置有些不符的地方:

    即使获取 var result = await _transactionsDataRepository... 也可能需要很长时间,因为有数百万行

    对于任何体面的数据库服务器来说,处理数百万行应该不是问题。听起来您的表上缺少一些索引。使用适当的索引,只要您能保持简单,您的查询应该会非常迅速。

    但这会是个好方法吗?

    不太清楚你在这里指的是好还是坏,但我会稍微解释一下......现在你正在向似乎包含重复数据的表写入大量行。当我想到基于事务的系统时,没有两个事务应该共享同一个 ID。这意味着对于两个不同的ClientIds,绝不应该出现t1.TransactionId == t2.TransactionId 的情况。现在您可以避免在我上面的 code-sn-p 中检查 ClientId。

    由于您只想为每个 TransactionId 保留 1 个事务,您是否需要有两个具有相同 TransactionId 的事务?如果没有,那么您可以更进一步,使TransactionId 列唯一,并避免插入具有相同TransactionId 的两行。您还可以使用 Entity Framework [Index(IsUnique=true)] 属性来创建索引以加快对该列/属性的查询。

    【讨论】:

    • 您的方法是将transToRemove 流式传输到应用程序并一次删除它们,还是发送查询并在数据库端删除它们? (我相信是前者)
    • 非常感谢您的回答!我在我的网络核心应用程序中使用 dapper,而不是 dbcontext。我也不能省略 clientId 检查,因为不同的客户端可以导入具有任何 id 的任何文件,并且它们可能相同。 (但自从它自动递增后就没有了)我不确定这是我的 azure 数据库问题还是缺少索引,但我需要为我的结果列表获取 1100 万个 id,这需要很长时间
    • @JamieTwells 我相信它会在 SQL 服务器上运行删除查询之前加载到应用程序的内存中。 See here 如何改进。
    • @user122222 好的,这当然是对客户端的限制。您仍然应该能够在这些列上创建索引以加快这些查询。我没有使用 Dapper,但我相信无论您需要将其转换为 SQL 查询还是其他方式,您都应该能够执行类似的操作。如果我的示例不足以引导您,我明天会看一下 Dapper 解决方案
    • @user122222 看看我的编辑,我希望这适用于 Dapper
    猜你喜欢
    • 1970-01-01
    • 2016-10-04
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 2017-04-05
    • 2018-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多