2010-08-17 4 views
6

Dopo aver cercato StackOverflow.com ho trovato diverse domande in cui si chiedeva come rimuovere i duplicati, ma nessuno di questi ha indirizzato la velocità.La tecnica più veloce per eliminare i dati duplicati

Nel mio caso ho una tabella con 10 colonne che contiene 5 milioni di duplicati di riga esatta. Inoltre, ho almeno un milione di altre righe con duplicati in 9 delle 10 colonne. La mia tecnica attuale sta prendendo (finora) 3 ore per eliminare questi 5 milioni di righe. Qui è il mio processo:

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

Avanti,

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

Eventuali suggerimenti su come accelerare questo, o un modo più veloce? Ricorda che dovrò eseguirlo di nuovo per le righe che non sono duplicati esatti.

Grazie mille.

AGGIORNAMENTO:
Ho dovuto interrompere il passaggio 2 per l'esecuzione al punto 9 ore. Ho provato il metodo di OMG Ponies e ho finito dopo soli 40 minuti. Ho provato il mio passaggio 2 con l'eliminazione batch di Andomar, ha funzionato 9 ore prima che l'avessi interrotto. AGGIORNAMENTO: Effettuato una query simile con un campo in meno per sbarazzarsi di un diverso set di duplicati e la query è stata eseguita per soli 4 minuti (8000 righe) utilizzando il metodo di OMG Ponies.

Proverò la tecnica cte alla prossima occasione, tuttavia, sospetto che il metodo di OMG Ponies sarà difficile da battere.

+1

Un paio di semplici ottimizzazioni alle vostre domande di cui sopra - non è necessario avere a, b, c, ecc nel 'select' della query principale - basta il PriKey, e rilasciare il HAVING - poi , nella seconda query solo 'DELETE FROM sourceTable WHERE PriKey NOT IN (SELEZIONA DT.MaxPriKey FROM #dupTemp DT)' –

+0

Grazie per il suggerimento. –

risposta

4

Che dire ESISTE:

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

Spiega perché pensi che in questo modo sarebbe più veloce. –

+1

@ sub13: EXISTS è diverso da JOIN o IN - restituisce true nella prima corrispondenza dei criteri. La teoria è meno lavoro dovrebbe essere uguale a una query più veloce. Su una nota correlata, [questo articolo] (http://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-is-null-sql-server/) spiegare e contrastare alcune opzioni. –

+0

Tutte le colonne all'interno di EXISTS() devono essere non null? –

0

Beh un sacco di cose differnt. In primo luogo sarebbe qualcosa di simile a questo lavoro (fare una selezione o assicurarsi che, forse anche messo in una tabella temporanea di essa la propria, #recordsToDelete):

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

Poi si può tabelle indice del temp, mettere un indice su prikey

Se si dispone di record in una tabella temporanea di quelli che si desidera eliminare, è possibile eliminare in batch, che è spesso più veloce di bloccare l'intera tabella con una cancellazione.

+0

Quando si tratta di colonne non null, 'NOT IN' e' NOT EXISTS' sono più efficienti: http://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left- join-is-null-sql-server/ –

3

Il collo di bottiglia nell'eliminazione di riga in blocco è in genere la transazione che SQL Server deve creare. Potresti essere in grado di accelerare notevolmente suddividendo la rimozione in transazioni più piccole. Ad esempio, per eliminare 100 righe alla volta:

while 1=1 
    begin 

    delete top 100 
    from sourceTable 
    ... 

    if @@rowcount = 0 
     break 
    end 
+0

Questa è un'idea molto interessante. Proverò sicuramente questo. –

+0

BTW: Non penso che cancellare la top 100 sia una sintassi valida –

+2

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

4

Puoi permetterti di avere il tavolo originale non disponibile per un breve periodo?

Penso che la soluzione più rapida sia creare una nuova tabella senza i duplicati. Fondamentalmente l'approccio che si usa con la tabella temporanea, ma invece di creare una tabella "regolare".

Quindi rilasciare la tabella originale e rinominare la tabella intermedia per avere lo stesso nome della tabella precedente.

+0

Sì. Un tavolo normale è più veloce di un tavolo temporaneo o qualcosa del genere? Per favore scusate la mia ignoranza :) –

+0

Probabilmente sarà la soluzione più rapida proposta finora - se ci sono chiavi straniere ecc. Questo diventa doloroso e soggetto a errori se non si presta attenzione, ma sicuramente vale la pena considerarlo. –

+1

@ subt13: hai bisogno del normale tavolo perché lo manterrai;) (contrariamente alla tua tabella temporanea) @WillA: sì, hai ragione, bisogna stare attenti con i vincoli. –

0

Ecco una versione in cui è possibile combinare entrambi i passaggi in un unico passaggio.

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

A proposito, avete degli indici che possono essere temporaneamente rimossi?

+1

Martin Smith ha mostrato l'altro giorno che il CTE può essere referenziato come origine DELETE, funzionando come una vista aggiornabile. –

+0

Ya, questa è una caratteristica interessante, non ero sicuro dell'efficienza rispetto a una vecchia tabella #temp. Ci vuole un po 'per fare qualsiasi cosa su queste molte righe. Ho un indice cluster. Se ne servono di più, posso certamente aggiungerli. –

1

... basato sul commento OMG Ponies sopra, un metodo CTE un po 'più compatto. Questo metodo fa miracoli sulle tabelle in cui hai (per qualsiasi motivo) nessuna chiave primaria - dove puoi avere righe identiche su tutte le colonne.

;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

Fresco. Pensavo di dare una mano e finisco per essere aiutato. Questo è un esecutore migliore del mio suggerimento. – bobs

+0

Questo è molto compatto, ma sono più interessato alla velocità. Da quello che ho letto e visto con ctes, sono solo zucchero sintattico nel mio caso. Per favore correggimi se sbaglio, comunque. –

+0

@ subt13: Dovrai farcelo sapere dopo aver confrontato il piano di query effettivo tra le varie opzioni. –