2010-08-17 8 views
6

Nach stackoverflow.com fand ich mehrere Fragen, wie man Duplikate entfernen kann, aber keiner von ihnen adressiert Geschwindigkeit.Schnellste Technik zum Löschen von doppelten Daten

In meinem Fall habe ich eine Tabelle mit 10 Spalten, die 5 Millionen exakte Reihe Duplikate enthält. Zusätzlich habe ich mindestens eine Million andere Zeilen mit Duplikaten in 9 der 10 Spalten. Meine derzeitige Technik nimmt (bisher) 3 Stunden, um diese 5 Millionen Zeilen zu löschen. Hier ist mein Prozess:

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

Als nächstes

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

Irgendwelche Tipps, wie dies zu beschleunigen, oder einen schnelleren Weg? Denken Sie daran, dass ich das für Zeilen wiederholen muss, die keine genauen Duplikate sind.

Vielen Dank.

UPDATE:
Ich musste Schritt 2 von der 9-Stunden-Marke zu stoppen. Ich habe die Methode von OMG Ponies versucht und sie ist nach 40 Minuten fertig. Ich versuchte meinen Schritt 2 mit Andomar's Batch löschen, es lief die 9 Stunden bevor ich es aufhörte. UPDATE: Eine ähnliche Abfrage mit einem Feld weniger ausgeführt, um eine andere Reihe von Duplikaten loszuwerden, und die Abfrage lief nur für 4 Minuten (8000 Zeilen) mit der Methode von OMG Ponies.

Ich werde versuchen, die CTE-Technik die nächste Chance, die ich bekomme, aber ich vermute, OMG Ponies 'Methode wird schwer zu schlagen sein.

+1

Ein paar einfache Optimierungen auf Ihre Fragen über - Sie brauchen nicht a, b, c usw. im 'SELECT' der Top-Abfrage - Sie die priKey gerade benötigen, und die HAVING fallen - dann , in der zweiten Abfrage nur 'DELETE FROM sourceTable WHERE PriKey NICHT IN (SELECT DT.MaxPriKey VON # dupTemp DT)' –

+0

Danke für den Tipp. –

Antwort

4

Was VORHANDEN:

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

Bitte erläutern Sie, warum Sie denken, dass dieser Weg schneller wäre. –

+1

@ sub13: EXISTS unterscheidet sich von JOIN oder IN - es gibt true bei der ersten Übereinstimmung der Kriterien zurück. Die Theorie ist weniger Arbeit sollte gleich eine schnellere Abfrage sein. Auf eine verwandte Anmerkung wird [dieser Artikel] (http://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left-join-isnull-sql-server/) Erklären und kontrastieren Sie einige Optionen. –

+0

Müssen alle Spalten innerhalb von EXISTS() nicht null sein? –

0

Nun viele differnt Dinge. Erstens wäre so etwas wie dieses Werk (do a select o sicher machen, vielleicht sogar in eine temporäre Tabelle setzen von seinen eigenen, #recordsToDelete):

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

Weiter Sie Index Temptabellen, einen Index für priKey

setzen können

Wenn Sie Datensätze in einer temporären Tabelle der zu löschenden Datensätze haben, können Sie in Batches löschen, was oft schneller ist als das Sperren der gesamten Tabelle durch Löschen.

+0

Beim Umgang mit Nicht-Null-Spalten sind 'NOT IN' und' NOT EXISTS' effizienter: http://explainextended.com/2009/09/15/not-in-vs-not-exists-vs-left- join-is-null-sql-server/ –

3

Der Engpass beim Löschen von Massenzeilen ist normalerweise die Transaktion, die SQL Server aufbauen muss. Sie können es möglicherweise erheblich beschleunigen, indem Sie das Entfernen in kleinere Transaktionen aufteilen. Um beispielsweise 100 Zeilen gleichzeitig zu löschen:

while 1=1 
    begin 

    delete top 100 
    from sourceTable 
    ... 

    if @@rowcount = 0 
     break 
    end 
+0

Das ist eine sehr interessante Idee. Ich werde das sicher versuchen. –

+0

BTW: Ich glaube nicht, löschen Sie Top 100 gültige Syntax ist –

+2

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

4

Können Sie es sich leisten, die ursprüngliche Tabelle für kurze Zeit nicht verfügbar zu haben?

Ich denke, die schnellste Lösung ist, eine neue Tabelle ohne die Duplikate zu erstellen. Im Grunde die Vorgehensweise, die Sie mit der temporären Tabelle verwenden, aber stattdessen eine "normale" Tabelle erstellen.

Dann die ursprüngliche Tabelle löschen und die Zwischentabelle umbenennen, um denselben Namen wie die alte Tabelle zu haben.

+0

Ja. Ist eine normale Tabelle schneller als eine temporäre Tabelle oder so? Bitte entschuldigen Sie meine Unwissenheit :) –

+0

Wahrscheinlich die schnellste Lösung, die bisher vorgeschlagen wurde - wenn es Fremdschlüssel usw. gibt, wird dies schmerzhaft und fehleranfällig, wenn Sie nicht aufpassen, aber definitiv eine Überlegung wert sind. –

+1

@ subt13: du brauchst die reguläre Tabelle, weil du sie behalten willst;) (im Gegensatz zu deiner Temp-Tabelle) @WillA: ja du hast Recht, man muss vorsichtig mit Einschränkungen umgehen. –

0

Hier ist eine Version, in der Sie beide Schritte in einem Schritt kombinieren können.

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

Übrigens, haben Sie irgendwelche Indizes, die vorübergehend entfernt werden können?

+1

Martin Smith zeigte neulich, dass der CTE als DELETE-Quelle referenziert werden kann und wie eine aktualisierbare Ansicht funktioniert. –

+0

Ya, das ist ein cooles Feature Ich war mir nur nicht sicher über die Effizienz im Vergleich zu einer alten Mode #temp Tabelle. Es dauert eine Weile, um etwas in diesen vielen Reihen zu tun. Ich habe einen Clustered-Index. Wenn mehr benötigt wird, kann ich sie sicherlich hinzufügen. –

1

... basierend auf OMG Ponies Kommentar oben, eine CTE-Methode, die ein wenig kompakter ist. Diese Methode wirkt Wunder bei Tabellen, wo Sie (aus welchen Gründen auch immer) keinen Primärschlüssel haben - wo Sie Zeilen haben können, die in allen Spalten identisch sind.

;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

Kühl. Ich dachte, ich würde aushelfen, und mir wird geholfen. Das ist ein besserer Interpret als mein Vorschlag. – bobs

+0

Das ist sehr kompakt, aber ich bin mehr an Geschwindigkeit interessiert. Nach dem, was ich mit ctes gelesen und gesehen habe, sind sie in meinem Fall nur syntaktischer Zucker. Bitte korrigiere mich, wenn ich falsch liege. –

+0

@ subT13: Sie müssen uns nach dem Vergleich des tatsächlichen Abfrageplans zwischen den verschiedenen Optionen wissen lassen. –