2010-04-07 168 views
14

Recibo un archivo XML diario que contiene miles de registros, cada uno de ellos es una transacción comercial que necesito almacenar en una base de datos interna para usar en informes y facturación . Tenía la impresión de que el archivo de cada día contenía solo registros únicos, pero descubrí que mi definición de único no es exactamente la misma que la del proveedor.Cómo evitar que se inserten registros duplicados con SqlBulkCopy cuando no hay clave principal

La aplicación actual que importa estos datos es una aplicación de consola C# .Net 3.5, lo hace usando SqlBulkCopy en una tabla de base de datos MS SQL Server 2008 donde las columnas coinciden exactamente con la estructura de los registros XML. Cada registro tiene un poco más de 100 campos, y no hay una clave natural en los datos, o mejor dicho, los campos que se me ocurren tienen sentido ya que una clave compuesta también tiene que permitir valores nulos. Actualmente, la tabla tiene varios índices, pero no una clave principal.

Básicamente, toda la fila debe ser única. Si un campo es diferente, es lo suficientemente válido para ser insertado. Consideré la creación de un hash MD5 de toda la fila, insertando eso en la base de datos y usando una restricción para evitar que SqlBulkCopy inserte la fila, pero no veo cómo poner el MD5 Hash en la operación BulkCopy y no estoy seguro si toda la operación fallaría y retrocedería si fallara un registro, o si continuaría.

El archivo contiene una gran cantidad de registros, yendo fila por fila en el XML, consultar la base de datos para un registro que coincida con todos los campos y luego decidir insertar es realmente la única forma que puedo ver de poder hacer esta. Solo esperaba no tener que volver a escribir la aplicación por completo, y la operación de copia masiva es mucho más rápida.

¿Alguien sabe de una manera de usar SqlBulkCopy mientras previene las filas duplicadas, sin una clave principal? O cualquier sugerencia para una forma diferente de hacer esto?

Respuesta

15

Cargaría los datos en una tabla de preparación y luego trataría los duplicados luego en la copia a la mesa final.

Por ejemplo, puede crear un índice (no único) en la tabla provisional para hacer frente a la "llave"

+1

Además, no agregue índices a su tabla de etapas hasta después de la importación masiva (es más rápido) – CResults

+0

@CResults: sí, debería haber mencionado que ... – gbn

+1

Bueno, eso definitivamente tiene sentido y es fácil de implementar. Gracias. – kscott

4

Me gustaría copiar a granel en una tabla temporal y luego insertar los datos de eso en la tabla de destino real. De esta forma, puede usar SQL para verificar y manejar duplicados.

+0

La idea de usar un hash es intrigante. La clave podría crearse fuera de la tabla temporal (donde podría manejar nulos). Por otro lado, si tiene algunos índices no exclusivos, puede incluir todas las coincidencias, si las hay, en algún subconjunto de columnas que probablemente sean únicas o casi únicas, y ejecutarlas para determinar la singularidad. – SeaDrive

1

¿Cuál es el volumen de datos? Tiene 2 opciones que puedo ver:

1: filtre en la fuente, implementando su propio IDataReader y usando algunos hash sobre los datos, y simplemente saltee los duplicados para que nunca pasen al TDS.

2: filtrar en la base de datos; en el nivel más simple, supongo que podría tener múltiples etapas de importación, los datos sin filtrar, y luego copiar los datos DISTINCT en sus tablas reales, tal vez usando una tabla intermedia si lo desea. Usted podría querer usar CHECKSUM para algo de esto, pero depende.

0

Y arregle esa tabla. Ninguna tabla debería tener un índice único, preferiblemente como PK. Incluso si agrega una clave sustituta porque no hay una clave natural, debe ser capaz de identificar específicamente un registro en particular. De lo contrario, ¿cómo va a deshacerse de los duplicados que ya tiene?

6

Dado que está utilizando SQL 2008, tiene dos opciones para resolver el problema fácilmente sin tener que cambiar su aplicación demasiado (si es que lo hace).

La primera solución posible es crear una segunda tabla como la primera pero con una clave de identidad sustituta y una restricción de exclusividad agregada usando la opción ignore_dup_key que hará todo el trabajo pesado de eliminar los duplicados para usted.

He aquí un ejemplo se puede ejecutar en SSMS para ver lo que está pasando:

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 

Alternativamente, también puede eliminar los duplicados en el lugar sin una segunda tabla, pero el rendimiento puede ser demasiado lento para sus necesidades . Aquí está el código para ese ejemplo, también ejecutable en 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

Creo que esto es mucho más limpio.

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

} 

De esta manera solo necesita una tabla de base de datos y puede mantener el código de Bussiness Logic.

0

Por qué no sólo tiene que utilizar, en lugar de una clave principal , crear un Índice dey establecer

Ignore Duplicate Keys: YES 

Esto hará prevent any duplicate key to fire an error, y no se creará (tal como existe ya).

enter image description here

que utiliza este método para insertar filas alrededor de 120.000 al día y funciona a la perfección.

+0

¿Existe algún límite práctico o difícil sobre cuántos campos se deben incluir en un índice? Cada fila de los datos en cuestión tiene más de 100 campos y cada campo debería estar en el índice. ¿No utilizaría esto una cantidad de recursos poco práctica? – kscott

+0

Necesita comprender qué es y para qué es un 'índice'; por ejemplo, esta opción' Ignorar claves duplicadas' solo se debe aplicar al 'documento_id' y mis otros dos índices son auxiliares para que la búsqueda pueda recuperarse mucho más rápidamente en una gran cantidad de registros mientras sigo buscando esos campos ... Pero debe haber un límite, aunque creo que es un límite de hardware (CPU + Memoria) y no una base de datos ... – balexandre

Cuestiones relacionadas