Как предотвратить вставку дубликатов записей с SqlBulkCopy, когда нет первичного ключа - PullRequest
17 голосов
/ 07 апреля 2010

Ежедневно я получаю XML-файл, содержащий тысячи записей, каждая из которых представляет собой бизнес-транзакцию, которую мне нужно сохранить во внутренней базе данных для использования при составлении отчетов и выставлении счетов. У меня сложилось впечатление, что файл каждого дня содержит только уникальные записи, но обнаружил, что мое определение уникальности не совсем совпадает с определением провайдера.

Текущее приложение, которое импортирует эти данные, является консольным приложением C # .Net 3.5, оно использует SqlBulkCopy в таблице базы данных MS SQL Server 2008, где столбцы точно соответствуют структуре записей XML. Каждая запись содержит чуть более 100 полей, и в данных нет естественного ключа, точнее, полей, которые я могу придумать, так как составной ключ в конечном итоге также должен допускать пустые значения. В настоящее время таблица имеет несколько индексов, но не имеет первичного ключа.

В основном весь ряд должен быть уникальным. Если одно поле отличается, оно является допустимым для вставки. Я смотрел на создание хеша MD5 для всей строки, вставку его в базу данных и использование ограничения, чтобы SqlBulkCopy не вставлял строку, но я не вижу, как получить хэш MD5 в операцию BulkCopy, и я не уверен, что вся операция завершится неудачно и откатится, если какая-либо одна запись не удалась или продолжится.

Файл содержит очень большое количество записей, построчно в XML, запрашивая в базе данных запись, которая соответствует всем полям, а затем решая вставить, - действительно единственный способ, которым я могу это сделать. , Я просто надеялся не переписывать приложение целиком, а операция массового копирования выполняется намного быстрее.

Кто-нибудь знает, как использовать SqlBulkCopy, предотвращая дублирование строк без первичного ключа? Или какие-либо предложения для другого способа сделать это?

Ответы [ 7 ]

17 голосов
/ 07 апреля 2010

Я бы загружал данные в промежуточную таблицу, а затем работал с дубликатами при копировании в финальную таблицу.

Например, вы можете создать (неуникальный) индекс в промежуточной таблице для работы с «ключом»

7 голосов
/ 08 апреля 2010

Учитывая, что вы используете 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
4 голосов
/ 07 апреля 2010

Я бы массово скопировал во временную таблицу и затем поместил данные из этой таблицы в фактическую таблицу назначения.Таким образом, вы можете использовать SQL для проверки и обработки дубликатов.

2 голосов
/ 22 декабря 2012

Почему бы просто не использовать вместо Первичный ключ , создать Индекс и установить

Ignore Duplicate Keys: YES

Это предотвратит любой дубликат ключа, чтобы вызвать ошибку , и он не будет создан (поскольку он уже существует).

enter image description here

Я использую этот метод для вставки около 120 000 строк в день и работает безупречно.

1 голос
/ 17 декабря 2010

Я думаю, что это намного чище.

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.

1 голос
/ 07 апреля 2010

Что такое объем данных?У меня есть 2 варианта, которые я вижу:

1: отфильтруйте его в источнике, внедрив свой собственный IDataReader и используя некоторый хэш для данных, и просто пропустите любые дубликаты, чтобы они никогда не передавались вTDS.

2: отфильтровать его в БД;на простейшем уровне, я думаю, вы могли бы выполнить несколько этапов импорта - необработанные, неанизированные данные - и затем скопировать данные DISTINCT в ваши фактические таблицы, возможно, используя промежуточную таблицу, если хотите.Вы могли бы хотеть использовать CHECKSUM для некоторых из них, но это зависит.

0 голосов
/ 07 апреля 2010

И починить эту таблицу. Ни одна таблица никогда не должна быть без уникального индекса, предпочтительно в виде PK. Даже если вы добавите суррогатный ключ из-за отсутствия естественного ключа, вы должны иметь возможность конкретно идентифицировать конкретную запись. Иначе как вы избавитесь от дубликатов, которые у вас уже есть?

...