2010-08-17 18 views
6

Después de buscar en stackoverflow.com encontré varias preguntas sobre cómo eliminar duplicados, pero ninguno de ellos abordaba la velocidad.La técnica más rápida para eliminar datos duplicados

En mi caso, tengo una tabla con 10 columnas que contiene 5 millones de duplicados de filas exactas. Además, tengo al menos un millón de filas con duplicados en 9 de las 10 columnas. Mi técnica actual está tomando (hasta ahora) 3 horas para borrar estas 5 millones de filas. Aquí está mi proceso:

-- Step 1: **This step took 13 minutes.** Insert only one of the n duplicate rows into a temp table 
select 
    MAX(prikey) as MaxPriKey, -- identity(1, 1) 
    a, 
    b, 
    c, 
    d, 
    e, 
    f, 
    g, 
    h, 
    i 
into #dupTemp 
FROM sourceTable 
group by 
    a, 
    b, 
    c, 
    d, 
    e, 
    f, 
    g, 
    h, 
    i 
having COUNT(*) > 1 

A continuación,

-- Step 2: **This step is taking the 3+ hours** 
-- delete the row when all the non-unique columns are the same (duplicates) and 
-- have a smaller prikey not equal to the max prikey 
delete 
from sourceTable 
from sourceTable 
inner join #dupTemp on 
    sourceTable.a = #dupTemp.a and 
    sourceTable.b = #dupTemp.b and 
    sourceTable.c = #dupTemp.c and 
    sourceTable.d = #dupTemp.d and 
    sourceTable.e = #dupTemp.e and 
    sourceTable.f = #dupTemp.f and 
    sourceTable.g = #dupTemp.g and 
    sourceTable.h = #dupTemp.h and 
    sourceTable.i = #dupTemp.i and 
    sourceTable.PriKey != #dupTemp.MaxPriKey 

Algún consejo sobre la manera de acelerar este proceso, o de una manera más rápida? Recuerde que tendré que volver a ejecutar esto para las filas que no sean duplicados exactos.

Muchas gracias.

ACTUALIZACIÓN:
Tuve que detener el paso 2 para que no se ejecutara en la marca de las 9 horas. Probé el método de Ponies de OMG y terminó después de solo 40 minutos. Probé mi paso 2 con la eliminación por lotes de Andomar, funcionó las 9 horas antes de que lo detuviera. ACTUALIZACIÓN: Se realizó una consulta similar con un campo menos para deshacerse de un conjunto diferente de duplicados y la consulta se ejecutó solo durante 4 minutos (8000 filas) utilizando el método de los Ponies de OMG.

Voy a probar la técnica cte la próxima vez que me lleguen, sin embargo, sospecho que el método de OMG Ponies será difícil de superar.

+1

Un par de sencillos optimizaciones a sus consultas por encima de - no es necesario tener a, b, c, etc., en el 'SELECT' de la consulta arriba - sólo tiene la PriKey, y soltar el HAVING - a continuación, , en la segunda consulta simplemente 'DELETE FROM sourceTable WHERE PriKey NOT IN (SELECCIONE DT.MaxPriKey FROM #dupTemp DT)' –

+0

Gracias por la sugerencia. –

Respuesta

4

¿Y existe:

DELETE FROM sourceTable 
WHERE EXISTS(SELECT NULL 
       FROM #dupTemp dt 
       WHERE sourceTable.a = dt.a 
       AND sourceTable.b = dt.b 
       AND sourceTable.c = dt.c 
       AND sourceTable.d = dt.d 
       AND sourceTable.e = dt.e 
       AND sourceTable.f = dt.f 
       AND sourceTable.g = dt.g 
       AND sourceTable.h = dt.h 
       AND sourceTable.i = dt.i 
       AND sourceTable.PriKey < dt.MaxPriKey) 
+0

Explique por qué cree que de esta manera sería más rápido. –

+1

@ sub13: EXISTS es diferente de JOIN o IN - devuelve verdadero en la primera coincidencia de los criterios. La teoría es menos trabajo debe ser igual a una consulta más rápida. En una nota relacionada, [este artículo] (http://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/) será explica y contrasta algunas opciones. –

+0

¿Es necesario que todas las columnas dentro de EXISTS() no sean nulas? –

0

un montón de pozos de cosas diferentes personas. En primer lugar sería algo así como este trabajo (hacer un selecto o Asegúrese de que, tal vez incluso puso en una tabla temporal de su propio, #recordsToDelete):

delete 
from sourceTable 
left join #dupTemp on 
     sourceTable.PriKey = #dupTemp.MaxPriKey 
where #dupTemp.MaxPriKey is null 

A continuación se puede tablas de índice temporales, poner un índice en prikey

Si tiene registros en una tabla temporal de los que desea eliminar, puede eliminarlos en lotes, lo que a menudo es más rápido que bloquear toda la tabla con una eliminación.

+0

Cuando se trata de columnas que no son nulas, 'NOT IN' y' NOT EXISTS' son más eficientes: http://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left- join-is-null-sql-server/ –

3

El cuello de botella en la eliminación masiva de filas suele ser la transacción que SQL Server debe generar. Es posible que pueda acelerarlo considerablemente dividiendo la eliminación en transacciones más pequeñas. Por ejemplo, para eliminar 100 filas a la vez:

while 1=1 
    begin 

    delete top 100 
    from sourceTable 
    ... 

    if @@rowcount = 0 
     break 
    end 
+0

Esa es una idea muy interesante. Estoy seguro de probar esto. –

+0

BTW: No creo que eliminar top 100 sea la sintaxis válida –

+2

@ subt13: Es - ver [SQL Server 2008 BOL - DELETE] (http://msdn.microsoft.com/en-us/library/ms189835.aspx) –

4

¿Puede permitirse tener la tabla original no disponible durante un corto período de tiempo?

Creo que la solución más rápida es crear una nueva tabla sin los duplicados. Básicamente, el enfoque que utiliza con la tabla temporal, pero en su lugar crea una tabla "regular".

A continuación, suelte la tabla original y cambie el nombre de la tabla intermedia para que tenga el mismo nombre que la tabla anterior.

+0

Sí. ¿Es una tabla regular más rápida que una tabla temporal o algo así? Disculpe mi ignorancia :) –

+0

Probablemente va a ser la solución más rápida propuesta hasta ahora: si hay claves externas, etc. esto se vuelve doloroso y propenso a errores si no tiene cuidado, pero definitivamente vale la pena considerarlo. –

+1

@ subt13: necesita la tabla normal porque la va a conservar;) (a diferencia de su tabla temporal) @WillA: sí, tiene razón, hay que tener cuidado con las limitaciones. –

0

Aquí hay una versión donde puede combinar ambos pasos en un solo paso.

WITH cte AS 
    (SELECT prikey, ROW_NUMBER() OVER (PARTITION BY a,b,c,d,e,f,g,h,i ORDER BY 
     prikey DESC) AS sequence 
    FROM sourceTable 
    ) 

DELETE 
FROM sourceTable 
WHERE prikey IN 
    (SELECT prikey 
    FROM cte 
    WHERE sequence > 1 
    ) ; 

Por cierto, ¿tiene algún índice que pueda eliminarse temporalmente?

+1

Martin Smith mostró el otro día que se puede hacer referencia al CTE como la fuente DELETE, funcionando como una vista actualizable. –

+0

Ya, esta es una característica interesante, simplemente no estaba seguro acerca de la eficiencia en comparación con una vieja mesa #temp. Lleva un tiempo hacer cualquier cosa en estas muchas filas. Tengo un índice agrupado. Si se necesitan más, ciertamente puedo agregarlos. –

1

... basado en los Potros OMG comentario anterior, un método CTE que es un poco más compacto. Este método funciona de maravilla en las tablas donde no tiene (por algún motivo) ninguna clave principal, donde puede tener filas que son idénticas en todas las columnas.

;WITH cte AS (
SELECT ROW_NUMBER() OVER 
      (PARTITION BY a,b,c,d,e,f,g,h,i ORDER BY prikey DESC) AS sequence 
    FROM sourceTable 
) 
DELETE 
FROM cte 
WHERE sequence > 1 
+0

Cool. Pensé que estaba ayudando, y terminé recibiendo ayuda. Este es un mejor desempeño que mi sugerencia. – bobs

+0

Esto es muy compacto, pero estoy más interesado en la velocidad. Por lo que he leído y visto con ctes, son meramente sintácticos en mi caso. Por favor corrígeme si me equivoco, sin embargo. –

+0

@ subt13: Deberá informarnos después de comparar el plan de consulta real entre las diversas opciones. –

Cuestiones relacionadas