Für exakt doppelte Zeilen, mit erhaltener Kopfzeile und unveränderter Reihenfolge:
awk 'NR==1 || !seen[$0]++' input.csv > deduped.csv
Das deckt den einfachen Fall ab, und der einfache Fall ist seltener, als man denkt. Die meisten „Duplikate“ in einer Arbeitsdatei sind keine identischen Zeilen, sondern zwei Datensätze für dieselbe Person mit anderer Groß- und Kleinschreibung, einem Leerzeichen am Ende oder einer aktualisierten Telefonnummer. Löschst du dabei den falschen, gehen Daten verloren. Auf die Reihenfolge kommt es also an: Lege fest, was zwei Zeilen zu denselben macht, sieh dir an, worin sie sich unterscheiden, und lösche erst dann.
Schritt eins: den Schlüssel festlegen
Ein Duplikat sind zwei Zeilen, die dieselbe Sache beschreiben. Welche Spalten das belegen, ist eine Entscheidung über deine Daten, keine Eigenschaft der Datei.
- Ein Duplikat der ganzen Zeile, jedes Feld identisch, ist fast immer ein Fehler: ein doppelter Import, ein erneut gelaufener Job, ein Anhängen, das zweimal lief. Das kannst du bedenkenlos entfernen.
- Ein Schlüssel-Duplikat, gleiche
emailoder gleicheorder_id, aber sonst alles verschieden, ist eine Datenfrage. Eine der Zeilen ist wahrscheinlich neuer oder vollständiger, und du musst dich entscheiden.
Wer das verwechselt, verliert auf die klassische Weise Datensätze. Wer in einer Kundenliste nach name dedupliziert, löscht fröhlich zwei verschiedene Menschen namens James Smith.
Schritt zwei: zählen, bevor du löschst
Führe nie blind eine Deduplizierung aus. Sieh dir an, was du entfernen willst:
duckdb -c "SELECT email, count(*) n FROM read_csv('input.csv') GROUP BY email HAVING n > 1 ORDER BY n DESC LIMIT 20"
Zwei Zahlen verraten dir, ob dein Schlüssel stimmt. Sind es eine Handvoll Duplikate, hast du wahrscheinlich echte Doppeleinträge. Ist ein Drittel der Datei „doppelt“, ist der Schlüssel falsch: Du vergleichst etwas, das nie eindeutig war.
Schau dir dann einen konkreten Fall vollständig an. Genau diesen Schritt überspringen die meisten:
duckdb -c "SELECT * FROM read_csv('input.csv') WHERE email = '[email protected]'"
Hier findest du heraus, dass sich die beiden Zeilen in genau der Spalte unterscheiden, auf die es dir ankam.
Schritt drei: entscheiden, welche Zeile bleibt
Sobald du weißt, dass sich die Zeilen unterscheiden, wird aus „Duplikate entfernen“ die Aufgabe „die richtige behalten“. Drei Regeln, grob danach sortiert, wie oft sie richtig sind:
Die neueste behalten. Gibt es einen Zeitstempel, ist das meistens die Antwort.
duckdb -c "COPY (SELECT * FROM (SELECT *, row_number() OVER (PARTITION BY email ORDER BY updated_at DESC) rn FROM read_csv('input.csv')) WHERE rn = 1) TO 'deduped.csv' (HEADER)"
Die vollständigste behalten. Ohne Zeitstempel ist die Zahl der nicht leeren Felder ein brauchbarer Ersatz, denn der vollere Datensatz ist meistens der spätere. Sortiere nach (col_a IS NOT NULL)::INT + (col_b IS NOT NULL)::INT + … und nimm die oberste.
Beide zusammenführen. Manchmal hat jede Zeile etwas, das der anderen fehlt. Das kommt oft vor, und eine Abkürzung gibt es nicht: Es ist ein Coalesce pro Spalte. Bei einer kleinen Menge machst du das besser von Hand, als es bei einer großen schlecht zu automatisieren.
Wofür du dich auch entscheidest: Schreibe die verworfenen Zeilen in eine zweite Datei, bevor du sie löschst. Eine Deduplizierung, die du nicht rückgängig machen kannst, wirst du in den nächsten zwei Wochen irgendwann bereuen.
Beinahe-Duplikate, also die meisten
Exakter Abgleich verfehlt den Großteil der echten Duplikate. Normalisiere zuerst, vergleiche danach und behalte die Originalwerte.
duckdb -c "SELECT lower(trim(email)) k, count(*) n FROM read_csv('input.csv') GROUP BY k HAVING n > 1"
Diese Normalisierungen helfen, grob nach Trefferquote geordnet:
- Groß- und Kleinschreibung.
[email protected]und[email protected]sind dasselbe Postfach. - Leerzeichen. Leerzeichen am Ende, die beim Kopieren entstehen, sind unsichtbar und extrem häufig.
trim()erwischt führende und folgende; einreplaceerledigt die geschützten Leerzeichen, die aus Webformularen kommen. - Satzzeichen in Kennungen. Telefonnummern wie
+44 20 7946 0958,02079460958und(020) 7946 0958sind eine Nummer. Entferne vor dem Vergleich alles, was keine Ziffer ist. - Gmail-Punkte und Plus-Adressen.
[email protected]landet im selben Posteingang wie[email protected]. Ob das als Duplikat zählt, hängt davon ab, ob du Personen oder Konten dedupliziert.
Was du nicht automatisch tun solltest: unscharfer Namensabgleich. „Jon Smith“ und „John Smith“ können dieselbe Person oder zwei Kollegen sein, und ein Ähnlichkeitsschwellwert, der für eine Datei passt, ist für die nächste falsch. Erzeuge die Kandidatenpaare, prüfe sie und handle erst dann.
Der SQL-Editor und der visuelle Filter-Editor sind die zwei Hälften dieser Aufgabe: ein GROUP BY, um die doppelten Schlüssel zu finden, dann ein Filter, um jede Zeile mit einem gemeinsamen Schlüssel zu lesen, ohne vorher etwas in eine Datenbank zu laden.
Was es kannIn einer Tabellenkalkulation
Excel und Google Sheets haben beide einen Befehl „Duplikate entfernen“ (Remove Duplicates), und eine Eigenschaft davon solltest du kennen, bevor du ihn benutzt: Er behält das erste Vorkommen und löscht den Rest, und du kannst nicht bestimmen, welches das erste ist. Die zufällige Reihenfolge im Blatt entscheidet, welcher Datensatz überlebt.
Sortiere also bewusst, bevor du ihn ausführst. Sortiere absteigend nach deinem Zeitstempel, und aus „das erste behalten“ wird „das neueste behalten“, was du meistens meintest.
Der andere Weg in der Tabelle, eine Hilfsspalte mit COUNTIF, ist langsamer, aber zerstörungsfrei, und er zeigt dir die Duplikate, statt sie zu entfernen. Bei einer großen Datei bringt er außerdem Excel zum Hängen, denn COUNTIF über eine halbe Million Zeilen wächst quadratisch.
Auf dem Smartphone
Auf dem Smartphone ist das realistisch kein vollständiger Deduplizierungsdurchlauf. Du sortierst nach der verdächtigen Spalte, scrollst und siehst die Wiederholungen nebeneinander. So findest du am schnellsten heraus, ob eine Datei überhaupt ein Duplikatproblem hat.
Wenn du sie siehst, beantwortet das Filtern auf einen Schlüssel und das Lesen der paar Zeilen, die ihn teilen, die einzige Frage, die zählt: Ist das derselbe Datensatz, oder sind es zwei Datensätze, die zufällig ein Feld gemeinsam haben?
Sortiere nach jeder Spalte wie in Excel und lösche dann Zeilen bei aktivem Filter, ohne etwas zu riskieren: Ausgeblendete Zeilen sind geschützt, ein Löschen berührt also nie Daten außerhalb des Filters. Rückgängig macht alles wieder gut, und genau das macht diese Arbeit auf dem Smartphone vertretbar.
Was es kannÜberprüfen
Drei Zahlen, und sie sollten zusammenpassen:
wc -l input.csv deduped.csv
duckdb -c "SELECT count(*) rows, count(DISTINCT email) keys FROM read_csv('deduped.csv')"
rows und keys müssen jetzt gleich sein. Sind sie es nicht, hat die Deduplizierung nicht jede Gruppe erfasst. Und input − deduped muss der Zahl der Zeilen entsprechen, die du entfernen wolltest und in Schritt zwei gezählt hast. Wurden mehr entfernt, war der Schlüssel weiter oder enger gefasst, als du dachtest, und die Datei mit den verworfenen Zeilen, die du gesichert hast, rettet dir jetzt den Nachmittag.