MySQL entfernt Duplikate schnell aus der großen Datenbank

70

Ich habe große (> Mil-Zeilen) MySQL-Datenbank durch Duplikate durcheinander gebracht. Ich denke, es könnte 1/4 bis 1/2 der gesamten Datenbank sein, die mit ihnen gefüllt ist. Ich muss sie schnell loswerden (ich meine Abfrageausführungszeit). So sieht es aus:
id (index) | text1 | text2 | Die
Kombination aus text3 text1 und text2 sollte eindeutig sein. Wenn Duplikate vorhanden sind, sollte nur eine Kombination mit text3 NOT NULL übrig bleiben. Beispiel:

1 | abc | def | NULL  
2 | abc | def | ghi  
3 | abc | def | jkl  
4 | aaa | bbb | NULL  
5 | aaa | bbb | NULL  

...wird:

1 | abc | def | ghi   #(doesn't realy matter id:2 or id:3 survives)   
2 | aaa | bbb | NULL  #(if there's no NOT NULL text3, NULL will do)

Neue IDs sind alles kalt, sie hängen nicht von alten Tabellen-IDs ab.
Ich habe Dinge ausprobiert wie:

CREATE TABLE tmp SELECT text1, text2, text3
FROM my_tbl;
GROUP BY text1, text2;
DROP TABLE my_tbl;
ALTER TABLE tmp RENAME TO my_tbl;

Oder SELECT DISTINCT und andere Variationen.
Während sie an kleinen Datenbanken arbeiten, ist die Ausführungszeit für Abfragen bei mir einfach riesig (eigentlich nie bis zum Ende;> 20 Minuten)

Gibt es einen schnelleren Weg, das zu tun? Bitte helfen Sie mir, dieses Problem zu lösen.

bizzz
quelle
2
Bitte geben Sie Folgendes an: a) Ist eine Umnummerierung des ID-Felds erforderlich? b) Welche Menge oder welches Verhältnis von Duplikaten erwarten wir? (nützlich, um zu entscheiden, ob vor Ort gearbeitet oder eine neue Tabelle erstellt werden soll) c) welche Indizes für die aktuelle Tabelle vorhanden sind.
MJV
a) Eine Umnummerierung des ID-Feldes ist nicht erforderlich. b) Meine Schätzung: Von 1/4 bis 1/2 der Datenbank sind Duplikate. c) ID ist der einzige Index. Ich werde die Frage entsprechend bearbeiten.
Bizzz

Antworten:

148

Ich glaube, das wird es schaffen, wenn Sie einen doppelten Schlüssel + ifnull () verwenden:

create table tmp like yourtable;

alter table tmp add unique (text1, text2);

insert into tmp select * from yourtable 
    on duplicate key update text3=ifnull(text3, values(text3));

rename table yourtable to deleteme, tmp to yourtable;

drop table deleteme;

Sollte viel schneller sein als alles, was eine Gruppierung nach oder eine Unterabfrage oder sogar eine Bestellung nach erfordert. Dies erfordert nicht einmal einen Dateisort, der die Leistung einer großen temporären Tabelle beeinträchtigt. Erfordert immer noch einen vollständigen Scan der Originaltabelle, aber das lässt sich nicht vermeiden.

ʞɔıu
quelle
Danke, es funktioniert! Aus 1,2-mil-Zeilen wurden in 60 Minuten 0,6 mil. Das sind also ungefähr 10000 Zeilen pro Minute. Danke auch für die klare Erklärung! :)
bizzz
Das war eine große Hilfe. Vielen Dank
rpearce
15
@ ʞɔıu (upsideDownNick) einfach und effektiv. Für diejenigen, die sich nicht für den Teil text3 not null interessieren, können Sie INSERT IGNORE verwenden (ohne Berücksichtigung des Teils ON DUPLICATE UPDATE). MySQL ignoriert Fehler und fügt nur den ersten eindeutigen Wert ein, den es findet (nachfolgende Duplikate werden ignoriert).
Tony Gil
+1 Dies ist eine intelligente Lösung. In meinem Fall verliert der Client nach etwa 10 Minuten die Verbindung zum Server (Tabelle enthält mehr als 45 Millionen Datensätze) und führt dazu, dass unordentliche Sperren geöffnet werden usw. - Gibt es eine Empfehlung, wie damit umzugehen ist?
Matt
Falls jemand interessiert ist, habe ich die @ ʞɔıu-Antwort mit FURTHER USE CASES auf stackoverflow.com/questions/3311903/… erweitert
César Revert-Gomar
95

Ich habe diesen einfachen einzeiligen Code gefunden, um genau das zu tun, was ich brauchte:

ALTER IGNORE TABLE dupTest ADD UNIQUE INDEX(a,b);

Entnommen aus: http://mediakey.dk/~cc/mysql-remove-duplicate-entries/

liorq
quelle
7
Sieht aus wie ein MySQL-Fehler, der verhindert, dass Ihre Abfrage (insbesondere ein IGNORETeil) funktioniert: Fehlercode: 1062 Doppelter Eintrag 'abc-def' für Schlüssel 'text1'
bizzz
12
@bizzz Sie müssen nur ausführen, set session old_alter_table=1wenn Sie diesen Fehler erhalten, und es dann erneut versuchen.
Matthew
Dies funktioniert nicht in der BLOB / TEXT-Spalte. es gibt Fehler "BLOB / TEXT Spalte 'Name' in der Schlüsselspezifikation ohne Schlüssellänge verwendet"
Dashrath
12
DELETE FROM dups
WHERE id NOT IN(
    SELECT id FROM (
        SELECT DISTINCT id, text1, text2
            FROM dups
        GROUP BY text1, text2
        ORDER BY text3 DESC
    ) as tmp
)

Dadurch werden alle Datensätze, Gruppen nach Unterscheidungsfeldern und Bestellungen nach ID abgefragt (dh wir wählen den ersten nicht null text3-Datensatz aus). Dann wählen wir die IDs aus diesem Ergebnis aus (dies sind gute IDs ... sie werden nicht gelöscht) und löschen alle IDs, die NICHT diese sind.

Eine solche Abfrage, die sich auf die gesamte Tabelle auswirkt, ist langsam. Sie müssen es nur ausführen und ausrollen lassen, damit Sie es in Zukunft verhindern können.

Nachdem Sie dieses "Update" durchgeführt haben, würde ich UNIQUE INDEX (text1, text2) auf diese Tabelle anwenden. Um die Möglichkeit von Duplikaten in Zukunft zu verhindern.

Wenn Sie die Route "Neue Tabelle erstellen und alte ersetzen" verwenden möchten. Sie können die sehr innere select-Anweisung verwenden, um Ihre insert-Anweisung zu erstellen.

MySQL-spezifisch (vorausgesetzt, die neue Tabelle heißt my_tbl2 und hat genau die gleiche Struktur):

INSERT INTO my_tbl2
    SELECT DISTINCT id, text1, text2, text3
            FROM dups
        GROUP BY text1, text2
        ORDER BY text3 DESC

Siehe MySQL INSERT ... SELECTWeitere Informationen finden .

Kevin Peno
quelle
Entschuldigung, beide Vorschläge löschen Duplikate, wählen aber nicht das richtige Text3-Feld, um zu überleben (NULL-Werte bleiben erhalten, solange es KEINE NULL-Alternativen gibt)
bizzz
8

Entfernen Sie Duplikate, ohne Fremdschlüssel zu entfernen

create table tmp like mytable;
ALTER TABLE tmp ADD UNIQUE INDEX(text1, text2, text3, text4, text5, text6);
insert IGNORE into tmp select * from mytable;
delete from mytable where id not in ( select id from tmp);
Gadelkareem
quelle
1
Dies sollte die richtige Antwort sein. Einfach und betriebsbereit.
PKHunter
3

Wenn Sie eine neue Tabelle erstellen können, verwenden Sie dazu einen eindeutigen Schlüssel in den Feldern text1 + text2. Fügen Sie dann Fehler in die Tabelle ein (unter Verwendung der INSERT IGNORE-Syntax):

select * from my_tbl order by text3 desc
  • Ich denke, die Reihenfolge von text3 desc wird die NULL zuletzt setzen, aber überprüfen Sie das noch einmal.

Indizes für all diese Spalten könnten viel helfen, aber das Erstellen könnte jetzt ziemlich langsam sein.

Scott Saunders
quelle
Es werden zuletzt Nullen gesetzt, aber die Anforderung "Behalte die erste, die in text3 keine Null enthält" wurde nicht erfüllt. Dazu müssen Sie nach ID ASC bestellen und Ihrer Anweisung einen WHERE-Text3 NICHT NULL hinzufügen.
Kevin Peno
Das ist ein guter Punkt. Diese Anforderung widerspricht jedoch seiner Stichprobenausgabe: 2 | aaa | bbb | NULL Vielleicht sagt er uns, was er wirklich will.
Scott Saunders
Ich habe seine Anfrage noch einmal gelesen. Es scheint, dass es ihm egal ist, solange, wenn es eine Nicht-Null gibt, Nicht-Nullen beibehalten werden. Ihr Beispiel würde also gut passen. :)
Kevin Peno
Danke, arbeiten. Mit einer Reihe von 1,2 mil dauerte es fast 3 Stunden; ca. 4000 Zeilen pro Minute geschrieben. Es bleibt ein Duplikat mit dem größten Text3-Feld und das entspricht meiner Datenbanklogik.
Bizzz
1

Bei großen Tabellen mit wenigen Duplikaten möchten Sie möglicherweise vermeiden, die gesamte Tabelle an einen anderen Ort zu kopieren. Eine Möglichkeit besteht darin, eine temporäre Tabelle mit den Zeilen zu erstellen, die Sie behalten möchten (für jeden Schlüssel mit Duplikaten), und dann Duplikate aus der Originaltabelle zu löschen.

Ein Beispiel wird hier gegeben .

user1931858
quelle
0

Ich habe nicht viel Erfahrung mit MySQL. Wenn es analytische Funktionen hat, versuchen Sie:

aus my_tbl löschen
 wo id in (
     ID auswählen 
       von (ID auswählen, Zeilennummer ()
                            over (Partition nach Text1, Text2 nach Text3 absteigend) als rn
               von my_tbl
               / * optional: wobei text1 'a%' mag * /
             ) als t2
       wo rn> 1
     )

Die optionale where-Klausel bedeutet, dass Sie sie mehrmals ausführen müssen, eine für jeden Buchstaben usw. Erstellen Sie einen Index für text1?

Vergewissern Sie sich vor dem Ausführen, dass "text desc" die letzten Nullen in MySQL sortiert.

Redcayuga
quelle
Sorry, Fehlercode: 1064 in der Nähe von '(Partition von ...'
bizzz
Ich denke, MySQL hat keine analytischen Funktionen. Ich werde es später noch einmal versuchen.
Redcayuga
Können Sie Folgendes ausführen: Erstellen Sie Tabellen-Dups als SELECT text1, text2, max (Fall, wenn text3 null ist, dann 1 sonst 0) als has_null3, max (Fall, wenn text3 nicht null ist, dann 1 sonst 0) als has_not_null3, min (Fall, wenn text3 ist nicht null dann id sonst null) als pref_id FROM my_tbl GROUP BY text1, text2 mit count (*)> 1 Dies gibt uns die Liste der duplizierten text1 / 2 und einige der "bevorzugten" IDs. Wenn es zu lange dauert und dies wahrscheinlich auch der Fall sein wird, fügen Sie "where text1 like 'a%'" oder ähnliches hinzu.
Redcayuga
0

Ich weiß, dass dies ein alter Thread ist, aber ich habe eine etwas chaotische Methode, die viel schneller und anpassbar ist. In Bezug auf die Geschwindigkeit würde ich 10 Sekunden anstelle von 100 Sekunden (10: 1) sagen.

Meine Methode erfordert all das chaotische Zeug, das Sie vermeiden wollten:

  • Gruppieren nach (und haben)
  • Gruppenkonzert mit ORDER BY
  • 2 temporäre Tabellen
  • Verwenden von Dateien auf der Festplatte!
  • irgendwie (php?) die datei danach löschen

Aber wenn Sie über MILLIONEN (oder in meinem Fall Zehn Millionen) sprechen, lohnt es sich.

Trotzdem ist es nicht viel, weil die Kommentare auf Portugiesisch sind, aber hier ist mein Beispiel:

EDIT : Wenn ich Kommentare bekomme, erkläre ich weiter, wie es funktioniert :)

START TRANSACTION;

DROP temporary table if exists to_delete;

CREATE temporary table to_delete as (
    SELECT
        -- escolhe todos os IDs duplicados menos os que ficam na BD
        -- A ordem de escolha dos IDs é dada por "ORDER BY campo_ordenacao DESC" em que o primeiro é o que fica
        right(
            group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ','),
            length(group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ',')) 
                - locate(",",group_concat(id ORDER BY campos_ordenacao DESC SEPARATOR ','))
        ) as ids,

        count(*) as c

    -- Tabela a eliminar duplicados
    FROM teste_dup

    -- campos a usar para identificar  duplicados
    group by test_campo1, test_campo2, teste_campoN
    having count(*) > 1 -- é duplicado
);

-- aumenta o limite desta variável de sistema para o máx 
SET SESSION group_concat_max_len=4294967295;

-- envia os ids todos a eliminar para um ficheiro
select group_concat(ids SEPARATOR ',') from to_delete INTO OUTFILE 'sql.dat';

DROP temporary table if exists del3;
create temporary table del3 as (select CAST(1 as signed) as ix LIMIT 0);

-- insere os ids a eliminar numa tabela temporaria a partir do ficheiro
load data infile 'sql.dat' INTO TABLE del3
LINES TERMINATED BY ',';

alter table del3 add index(ix);

-- elimina os ids seleccionados
DELETE teste_dup -- tabela 
from teste_dup -- tabela

join del3 on id=ix;

COMMIT;
JDuarteDJ
quelle
0

Mit dieser einfachen Abfrage können Sie alle doppelten Einträge entfernen. Dadurch werden alle doppelten Datensätze ausgewählt und entfernt.

 DELETE i1 
FROM TABLE i1
LEFT JOIN TABLE i2
  ON i1.id = i2.id
 AND i1.colo = i2.customer_invoice_id
 AND i1.id < i2.id
WHERE i2.customer_invoice_id IS NOT NULL
Kamran Sheikh
quelle