2011-07-06 22 views
12

Estoy intentando insertar muchos registros usando la instrucción MERGE de T-SQL, pero mi consulta no puede INSERTAR cuando hay registros duplicados en la tabla de origen. El fallo se debe a:Cómo evitar la inserción de registros duplicados al utilizar una instrucción de combinación de T-SQL

  1. la tabla de destino tiene una clave principal basado en dos columnas
  2. la tabla de origen puede contener registros duplicados que violan la restricción de clave principal de la tabla de destino ("Violación de restricción PRIMARY KEY" es lanzado)

Estoy buscando una forma de cambiar mi declaración MERGE para que ignore los registros duplicados dentro de la tabla fuente y/o intente capturar la instrucción INSERT para detectar excepciones que puedan ocurrir (es decir, todas otras instrucciones INSERT se ejecutarán independientemente de los pocos huevos malos que puedan ocurrir) o, tal vez, haya una mejor manera de o ¿sobre este problema?

Aquí hay un ejemplo de consulta de lo que intento explicar. El siguiente ejemplo se sumará 100 mil registros a una tabla temporal y luego intentará insertar los registros en la tabla de destino -

EDITAR En mi post original sólo incluía dos campos en el ejemplo mesas, que dio paso a SO amigos para dar una solución DISTINCT para evitar duplicados en la instrucción MERGE. Debería haber mencionado que en mi problema del mundo real las tablas tienen 15 campos y de esos 15, dos de los campos son CLAVES PRIMARIAS CLAVES. Por lo tanto, la palabra clave DISTINCT no funciona porque necesito SELECCIONAR los 15 campos e ignorar los duplicados en función de dos de los campos.

He actualizado la consulta a continuación para incluir un campo más, col4. Necesito incluir col4 en MERGE, pero solo necesito asegurarme de que SOLO col2 y col3 sean únicos.

-- Create the source table 
CREATE TABLE #tmp (
col2 datetime NOT NULL, 
col3 int NOT NULL, 
col4 int 
) 
GO 

-- Add a bunch of test data to the source table 
-- For testing purposes, allow duplicate records to be added to this table 
DECLARE @loopCount int = 100000 
DECLARE @loopCounter int = 0 
DECLARE @randDateOffset int 
DECLARE @col2 datetime 
DECLARE @col3 int 
DECLARE @col4 int 

WHILE (@loopCounter) < @loopCount 
BEGIN 
    SET @randDateOffset = RAND() * 100000 
    SET @col2 = DATEADD(MI,@randDateOffset,GETDATE()) 
    SET @col3 = RAND() * 1000 
    SET @col4 = RAND() * 10 
    INSERT INTO #tmp 
    (col2,col3,col4) 
    VALUES 
    (@col2,@col3,@col4); 

    SET @loopCounter = @loopCounter + 1 
END 

-- Insert the source data into the target table 
-- How do we make sure we don't attempt to INSERT a duplicate record? Or how can we 
-- catch exceptions? Or? 
MERGE INTO dbo.tbl1 AS tbl 
    USING (SELECT * FROM #tmp) AS src 
    ON (tbl.col2 = src.col2 AND tbl.col3 = src.col3) 
    WHEN NOT MATCHED THEN 
     INSERT (col2,col3,col4) 
     VALUES (src.col2,src.col3,src.col4); 
GO 
+0

Tienes que decidir qué fila debes elegir col4 cuando hay duplicados para col2 y col3 en #tmp. Por ejemplo, puede usar 'group by col2, col3' y' min (col4) como col4'. –

Respuesta

15

Resuelto a su nueva especificación. Solo insertando el valor más alto de col4: Esta vez usé un grupo para evitar filas duplicadas.

MERGE INTO dbo.tbl1 AS tbl 
USING (SELECT col2,col3, max(col4) col4 FROM #tmp group by col2,col3) AS src 
ON (tbl.col2 = src.col2 AND tbl.col3 = src.col3) 
WHEN NOT MATCHED THEN 
    INSERT (col2,col3,col4) 
    VALUES (src.col2,src.col3,src.col4); 
+0

Cometí el error de usar solo dos campos en mi ejemplo de consulta. El hecho es que mi tabla objetivo tiene más de dos campos. Sin embargo, son solo dos campos los que componen el PK Cluster. Entonces la solución DISTINCT que sugirió no será suficiente. Actualicé mi publicación original para reflejar los campos adicionales. A pesar de todo, gracias por su respuesta (y +1 para responder la pregunta tal como está). – Jed

+1

Ok, le di una nueva oportunidad. Espero haber entendido bien. –

7

Dado que la fuente tiene duplicados y no está utilizando MERGE por completo, usaría un INSERTAR.

INSERT dbo.tbl1 (col2,col3) 
SELECT DISTINCT col2,col3 
FROM #tmp src 
WHERE NOT EXISTS (
     SELECT * 
     FROM dbo.tbl1 tbl 
     WHERE tbl.col2 = src.col2 AND tbl.col3 = src.col3) 

La razón por la que MERGE falla es porque no está marcada fila por fila. Se encuentran todas las coincidencias, luego intenta INSERTAR todo esto. No comprueba si hay filas en el mismo lote que ya coinciden.

Esto me recuerda un poco a la "Halloween problem" cuando cambian los datos iniciales de una operación atómica afectan los cambios de datos posteriores: no es correcto

+0

No he probado mi propio script, ¿estás diciendo que falla? –

+0

@ t-clausen.dk: También tiene una DISTINCIÓN, así que debería estar bien. Teniendo en cuenta los límites de MERGE, ¿por qué no usar solo un INSERT? ... – gbn

+0

Debido al título de la pregunta –

2

En lugar de GROUP BY puede utilizar una función analítica, que le permite seleccionar un registro específico en el conjunto de registros duplicados para combinar.

MERGE INTO dbo.tbl1 AS tbl 
USING (
    SELECT * 
    FROM (
     SELECT *, ROW_NUMBER() OVER (PARTITION BY col2, col3 ORDER BY ModifiedDate DESC) AS Rn 
     FROM #tmp 
    ) t 
    WHERE Rn = 1 --choose the most recently modified record 
) AS src 
ON (tbl.col2 = src.col2 AND tbl.col3 = src.col3) 
Cuestiones relacionadas