2010-04-07 132 views
14

我收到包含數千條記錄的每日XML文件,每條記錄都是我需要存儲在內部數據庫中以用於報告和帳單的業務事務。 我的印象是,每天的文件只包含獨特的記錄,但發現我的獨特定義與供應商的定義不完全相同。如何防止在沒有主鍵時使用SqlBulkCopy插入重複記錄

導入此數據的當前應用程序是C#.Net 3.5控制檯應用程序,它將SqlBulkCopy用於MS SQL Server 2008數據庫表中,其中的列與XML記錄的結構完全匹配。每個記錄有超過100個字段,並且數據中沒有自然鍵,或者我可以想到的作爲複合鍵的字段最終也必須允許空值。目前該表有幾個索引,但沒有主鍵。

基本上整個行必須是唯一的。如果一個字段不同,那麼它的有效性足以被插入。我着眼於創建整行的MD5散列,將其插入數據庫,並使用約束來阻止SqlBulkCopy插入行,但我看不到如何將MD5散列加入BulkCopy操作,而我不是確定整個操作是否會失敗,並在任何一條記錄失敗時回滾,或者是否會繼續。

該文件包含非常大量的記錄,在XML中逐行進行查詢,查詢數據庫中是否有匹配所有字段的記錄,然後決定插入是我能夠看到的唯一方法這個。我只是希望不必完全重寫應用程序,而且大容量複製操作要快得多。

有誰知道一種方法來使用SqlBulkCopy,同時防止重複行,沒有主鍵?或者有什麼建議以不同的方式來做到這一點?

回答

15

我會將數據上傳到暫存表中,然後處理重複的副本到最終表中。

例如,您可以創建臨時表(非唯一)的索引來處理的「鑰匙」

+1

另外,請勿在大容量導入(更快)之後向臨時表中添加索引 – CResults 2010-04-07 15:36:40

+0

@CResults:是的,應該提到的是...... – gbn 2010-04-07 15:42:12

+1

那麼這個確實有意義且易於實現。謝謝。 – kscott 2010-04-07 15:57:58

4

我會批量複製到臨時表中,然後將數據從其中推送到實際的目標表中。通過這種方式,您可以使用SQL來檢查和處理重複項。

+0

使用散列的想法很有趣。可以從臨時表(可以處理空值)創建密鑰。另一方面,如果您確實有一些非唯一索引,則可以將所有匹配(如果有的話)引入可能是唯一或接近唯一的列的某個子集,然後遍歷它們以確定唯一性。 – SeaDrive 2010-04-07 16:23:55

1

什麼是數據量?你有2個選項,我可以看到:

1:通過實施你自己的IDataReader並對數據使用一些散列,並簡單地跳過任何重複項,以使它們永遠不會傳入TDS,從而在源處過濾它。 2:在DB中過濾它;在最簡單的級別上,我想你可能會有多個導入階段 - 原始未經數據處理的數據 - 然後將DISTINCT數據複製到您的實際表中,如果需要可以使用中間表。你可能想要使用CHECKSUM這一些,但它取決於。

0

並修復該表。沒有一個表應該沒有獨特的索引,最好作爲PK。即使您添加代理鍵因爲沒有自然鍵,您需要能夠專門識別特定的記錄。否則,你將如何擺脫你已有的副本?

6

鑑於您使用的是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 
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保留在代碼中。

0

爲什麼不直接使用,而不是主鍵,創建一個指數並設置

Ignore Duplicate Keys: YES 

這將prevent any duplicate key to fire an error,它將不會被創建(因爲它已經存在)。

enter image description here

我用這個方法來插入每天大約120.000行和完美的作品。

+0

索引中應包含多少個字段是否存在任何困難或實際限制?所討論數據的每一行有超過100個字段,每個字段都需要在索引中。這不會使用不切實際的資源嗎? – kscott 2012-12-21 22:34:11

+0

您需要了解'index'的作用和用途,例如,'Ignore Duplicate Keys'選項僅適用於'document_id',而我的其他兩個索引是助手,因此可以更方便地檢索搜索當我不斷搜索這些字段時,快速處理大量的記錄......但是必須有一個限制,儘管我認爲它是一個硬件限制(CPU +內存)而不是數據庫一個...... – balexandre 2012-12-22 06:40:20