18

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

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

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

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

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

4

7 回答 7

17

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

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

于 2010-04-07T15:26:47.207 回答
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
于 2010-04-08T04:13:12.617 回答
5

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

Ignore Duplicate Keys: YES

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

在此处输入图像描述

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

于 2012-12-21T20:56:04.553 回答
4

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

于 2010-04-07T15:25:44.910 回答
1

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

1:在源头过滤它,通过实现你自己的IDataReader并对数据使用一些散列,并简单地跳过任何重复项,这样它们就不会被传递到 TDS 中。

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

于 2010-04-07T15:29:17.510 回答
1

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

于 2010-04-07T17:09:22.700 回答
0

我认为这更清洁。

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

}

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

于 2010-12-17T14:26:59.233 回答