Как предотвратить вставку повторяющихся записей с помощью SqlBulkCopy при отсутствии первичного ключа

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

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

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

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

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


person kscott    schedule 07.04.2010    source источник


Ответы (7)


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

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

person gbn    schedule 07.04.2010
comment
Кроме того, не добавляйте индексы в промежуточную таблицу до завершения массового импорта (это быстрее) - person CResults; 07.04.2010
comment
Что ж, это определенно имеет смысл и легко реализуется. Спасибо. - person kscott; 07.04.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
person Sean    schedule 08.04.2010

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

person Thomas    schedule 07.04.2010
comment
Идея использования хеша интригует. Ключ может быть создан из временной таблицы (где вы можете обрабатывать нули). С другой стороны, если у вас есть несколько неуникальных индексов, вы можете извлечь все совпадения, если таковые имеются, для некоторого подмножества столбцов, которые могут быть уникальными или почти уникальными, и просмотреть их, чтобы определить уникальность. - person SeaDrive; 07.04.2010

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

Ignore Duplicate Keys: YES

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

введите описание изображения здесь

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

person balexandre    schedule 21.12.2012
comment
Есть ли какое-то жесткое или практическое ограничение на количество полей, которые должны быть включены в индекс? Каждая строка рассматриваемых данных содержит более 100 полей, и каждое поле должно быть в индексе. Разве для этого не потребуется непрактичное количество ресурсов? - person kscott; 22.12.2012
comment
Вам нужно понимать, что делает и для чего предназначен index, например, этот параметр Ignore Duplicate Keys должен применяться только к document_id, а два других моих индекса являются помощниками, поэтому поиск можно быстрее получить по огромному количеству записей, так как Я продолжаю искать эти поля ... Но должен быть предел, хотя я думаю, что это ограничение на оборудование (ЦП + память), а не на базу данных ... - person balexandre; 22.12.2012

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

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

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

person Marc Gravell    schedule 07.04.2010

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

person HLGEM    schedule 07.04.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 );

}

Таким образом, потребуется только одна таблица базы данных и можно будет сохранить бизнес-логику в коде.

person John    schedule 17.12.2010
comment
это предотвратит вставку строк, которые дублируются в файл, но не предотвратит выдачу исключения дублированного ключа при попытке добавить строку из файла, который уже присутствует в таблице. - person Fabio Napodano; 08.10.2019