如何防止在没有主键时使用SqlBulkCopy插入重复记录

时间:2010-04-07 15:22:27

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

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

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

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

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

有没有人知道在不使用主键的情况下防止重复行时使用SqlBulkCopy的方法?或者以不同的方式做任何建议?

7 个答案:

答案 0 :(得分:17)

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

例如,您可以在登台表上创建(非唯一)索引来处理“密钥”

答案 1 :(得分:7)

鉴于您使用的是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

答案 2 :(得分:4)

我将批量复制到临时表中,然后将数据从该表推送到实际的目标表中。通过这种方式,您可以使用SQL来检查和处理重复项。

答案 3 :(得分:1)

什么是数据量?你有2个选项我可以看到:

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

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

答案 4 :(得分:1)

我认为这更清洁了。

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 );

}

这种方式只需要一个数据库表,并且可以在代码中保留Bussiness Logic。

答案 5 :(得分:1)

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

Ignore Duplicate Keys: YES

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

enter image description here

我使用这种方法每天插入大约120,000行,并且完美无瑕地工作。

答案 6 :(得分:0)

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