【问题标题】:How to prevent duplicate records being inserted with SqlBulkCopy when there is no primary key没有主键时如何防止使用 SqlBulkCopy 插入重复记录
【发布时间】:2012-12-22 04:56:04
【问题描述】:

我每天收到一个包含数千条记录的 XML 文件,每条记录都是我需要存储在内部数据库中以用于报告和计费的业务交易。 我的印象是每天的文件只包含唯一的记录,但发现我对唯一的定义与提供者的定义并不完全相同。

当前导入此数据的应用程序是一个 C#.Net 3.5 控制台应用程序,它使用 SqlBulkCopy 到 MS SQL Server 2008 数据库表中,其中列与 XML 记录的结构完全匹配。每条记录只有 100 多个字段,数据中没有自然键,或者更确切地说,我可以想出的字段作为复合键最终也必须允许空值。目前该表有多个索引,但没有主键。

基本上整行都需要是唯一的。如果一个字段不同,它就足够有效,可以插入。我查看了创建整行的 MD5 哈希,将其插入数据库并使用约束来防止 SqlBulkCopy 插入该行,但我不知道如何将 MD5 哈希放入 BulkCopy 操作中,我不是确定如果任何一条记录失败,整个操作是否会失败并回滚,或者是否会继续。

该文件包含大量记录,在 XML 中逐行查找,查询数据库以查找与所有字段匹配的记录,然后决定插入确实是我认为能够做到这一点的唯一方法.我只是希望不必完全重写应用程序,并且批量复制操作要快得多。

有谁知道在没有主键的情况下使用 SqlBulkCopy 防止重复行的方法?或者有什么不同的方法可以做到这一点的建议?

【问题讨论】:

    标签: c# sql sql-server sql-server-2008 sqlbulkcopy


    【解决方案1】:

    我会将数据上传到临时表中,然后在复制到最终表时处理重复项。

    例如,你可以在临时表上创建一个(非唯一的)索引来处理“key”

    【讨论】:

    • 此外,在批量导入之前不要向临时表添加索引(它更快)
    • 嗯,这绝对是有道理的,而且很容易实现。谢谢。
    【解决方案2】:

    鉴于您使用的是 SQL 2008,您有两种选择可以轻松解决问题,而无需对应用程序进行太多更改(如果有的话)。

    第一个可能的解决方案是创建第二个表,就像第一个表一样,但使用 ignore_dup_key 选项添加代理标识键和唯一性约束,这将为您完成消除重复项的所有繁重工作。

    这是一个您可以在 SSMS 中运行以查看发生了什么的示例:

    if object_id( 'tempdb..#test1' ) is not null drop table #test1;
    if object_id( 'tempdb..#test2' ) is not null drop table #test2;
    go
    
    
    -- example heap table with duplicate record
    
    create table #test1
    (
         col1 int
        ,col2 varchar(50)
        ,col3 char(3)
    );
    insert #test1( col1, col2, col3 )
    values
         ( 250, 'Joe''s IT Consulting and Bait Shop', null )
        ,( 120, 'Mary''s Dry Cleaning and Taxidermy', 'ACK' )
        ,( 250, 'Joe''s IT Consulting and Bait Shop', null )    -- dup record
        ,( 666, 'The Honest Politician', 'LIE' )
        ,( 100, 'My Invisible Friend', 'WHO' )
    ;
    go
    
    
    -- secondary table for removing duplicates
    
    create table #test2
    (
         sk int not null identity primary key
        ,col1 int
        ,col2 varchar(50)
        ,col3 char(3)
    
        -- add a uniqueness constraint to filter dups
        ,constraint UQ_test2 unique ( col1, col2, col3 ) with ( ignore_dup_key = on )
    );
    go
    
    
    -- insert all records from original table
    -- this should generate a warning if duplicate records were ignored
    
    insert #test2( col1, col2, col3 )
    select col1, col2, col3
    from #test1;
    go
    

    或者,您也可以在没有第二个表的情况下就地删除重复项,但性能可能太慢,无法满足您的需求。这是该示例的代码,也可在 SSMS 中运行:

    if object_id( 'tempdb..#test1' ) is not null drop table #test1;
    go
    
    
    -- example heap table with duplicate record
    
    create table #test1
    (
         col1 int
        ,col2 varchar(50)
        ,col3 char(3)
    );
    insert #test1( col1, col2, col3 )
    values
         ( 250, 'Joe''s IT Consulting and Bait Shop', null )
        ,( 120, 'Mary''s Dry Cleaning and Taxidermy', 'ACK' )
        ,( 250, 'Joe''s IT Consulting and Bait Shop', null )    -- dup record
        ,( 666, 'The Honest Politician', 'LIE' )
        ,( 100, 'My Invisible Friend', 'WHO' )
    ;
    go
    
    
    -- add temporary PK and index
    
    alter table #test1 add sk int not null identity constraint PK_test1 primary key clustered;
    create index IX_test1 on #test1( col1, col2, col3 );
    go
    
    
    -- note: rebuilding the indexes may or may not provide a performance benefit
    
    alter index PK_test1 on #test1 rebuild;
    alter index IX_test1 on #test1 rebuild;
    go
    
    
    -- remove duplicates
    
    with ranks as
    (
        select
             sk
            ,ordinal = row_number() over 
             ( 
                -- put all the columns composing uniqueness into the partition
                partition by col1, col2, col3
                order by sk
             )
        from #test1
    )
    delete 
    from ranks
    where ordinal > 1;
    go
    
    
    -- remove added columns
    
    drop index IX_test1 on #test1;
    alter table #test1 drop constraint PK_test1;
    alter table #test1 drop column sk;
    go
    

    【讨论】:

      【解决方案3】:

      为什么不简单地使用,而不是主键,创建一个索引并设置

      Ignore Duplicate Keys: YES
      

      这将是prevent any duplicate key to fire an error,并且不会被创建(因为它已经存在)。

      我使用这种方法每天插入大约 120.000 行并且工作完美。

      【讨论】:

      • 索引中应包含多少字段是否有硬性或实际限制?相关数据的每一行都有超过 100 个字段,并且每个字段都需要在索引中。这不会使用不切实际的资源吗?
      • 您需要了解index 的作用和用途,例如,此Ignore Duplicate Keys 选项仅适用于document_id,而我的其他两个索引是助手,因此搜索当我不断搜索这些字段时,可以在大量记录上更快地检索到......但必须有一个限制,尽管我认为这是一个硬件限制(CPU + 内存)而不是数据库......
      【解决方案4】:

      我会批量复制到一个临时表中,然后将其中的数据推送到实际的目标表中。这样,您就可以使用 SQL 来检查和处理重复项。

      【讨论】:

      • 使用哈希的想法很有趣。密钥可以从临时表中创建(您可以在其中处理空值)。另一方面,如果您确实有一些非唯一索引,则可以将所有匹配项(如果有)拉入可能是唯一或接近唯一的列的某个子集,并遍历它们以确定唯一性。
      【解决方案5】:

      数据量是多少?我可以看到您有 2 个选项:

      1:通过实现您自己的 IDataReader 并对数据使用一些散列,并简单地跳过任何重复项,以便它们永远不会传递到 TDS 中,从源头对其进行过滤。

      2:在数据库中过滤;在最简单的层面上,我猜您可能有多个导入阶段 - 原始的、未经处理的数据 - 然后将 DISTINCT 数据复制到您的 actual 表中,如果您愿意,可能使用中间表.您可能想要使用CHECKSUM 来处理其中的一些问题,但这取决于。

      【讨论】:

        【解决方案6】:

        并修复那张桌子。任何表都不应该没有唯一索引,最好是 PK。即使您因为没有自然键而添加代理键,您也需要能够专门识别特定记录。否则你将如何摆脱你已经拥有的重复?

        【讨论】:

          【解决方案7】:

          我认为这更清洁。

          var dtcolumns = new string[] { "Col1", "Col2", "Col3"};
          
          var dtDistinct = dt.DefaultView.ToTable(true, dtcolumns);
          
          using (SqlConnection cn = new SqlConnection(cn) 
          {
                          copy.ColumnMappings.Add(0, 0);
                          copy.ColumnMappings.Add(1, 1);
                          copy.ColumnMappings.Add(2, 2);
                          copy.DestinationTableName = "TableNameToMapTo";
                          copy.WriteToServer(dtDistinct );
          
          }
          

          这种方式只需要一张数据库表,可以将业务逻辑保留在代码中。

          【讨论】:

          • 这将阻止将重复的行插入到文件中,但不会阻止当您尝试从表中已存在的文件中添加行时引发重复键异常
          猜你喜欢
          • 2017-08-13
          • 2016-08-24
          • 2015-04-14
          • 1970-01-01
          • 2012-10-14
          • 1970-01-01
          • 1970-01-01
          • 2019-09-28
          • 1970-01-01
          相关资源
          最近更新 更多