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

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

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

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

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

有没有人知道一种方法来使用SqlBulkCopy,同时防止重复行,没有主键? 或者以不同的方式做任何建议吗?

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

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

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

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

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

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

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

我认为这更清洁了。

 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。

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

 Ignore Duplicate Keys: YES 

这将防止任何重复键触发错误 ,并且不会创建它(因为它已经存在)。

在此处输入图像描述

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

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