Cloud allgemein

Data Deduplication: Hash, Fuzzy Matching und Record Linkage im Praxiseinsatz

Dein CRM hat 847.000 Kund:innen-Records. Analytics sagt +40 % Umsatz, Finance sagt +22 %. Drei Strategien gegen Duplikate — von Hash bis probabilistischem Record Linkage.

Harbinger Team14. Mai 20265 Min. LesezeitAktualisiert 14.5.2026
  • data-quality
  • deduplication
  • fuzzy-matching
  • record-linkage
  • entity-resolution
  • data-engineering
Inhaltsverzeichnis9 Abschnitte

Dein CRM hat 847.000 Kund:innen-Records. Analytics sagt +40 % Umsatz, Finance sagt +22 %. Die Differenz sind Duplikate — dieselbe Person dreimal gezählt unter leicht unterschiedlichen Namen und Mails. Deduplication ist die Disziplin, mit der diese Zahlen konvergieren.

TL;DR

  • Drei Klassen von Duplikaten brauchen drei Strategien.
  • Hash-basiert: exakte Duplikate aus doppelter Ingestion — schnell und deterministisch.
  • Fuzzy Matching: Tippfehler und Formatunterschiede — Schwellenwert mit Labeled Data validieren.
  • Record Linkage: verschiedene Datasets ohne gemeinsamen Key — probabilistisch.
  • Produktion: alle drei in einer Pipeline, plus Human Review.

Drei Ursachen, drei Strategien

UrsacheBeispielBeste Methode
Exakte technische DuplikateDieselbe Zeile zweimalHash-basiert
Tippfehler, Format"John Smith" vs "Jon Smith"Fuzzy Matching
Cross-System-EntityPerson in CRM + ERPRecord Linkage

Strategie 1 — Hash-basierte Deduplication

Das schnellste und zuverlässigste Tool für exakte Duplikate. Du berechnest einen deterministischen Hash der Unique-Felder und dedupst über den Hash.

Richtig, wenn Duplikate kommen aus:

  • Doppel-Ingestion derselben Datei
  • Retry-Logik ohne Idempotenz
  • Mehrere Quellen schreiben dieselben Events
import pandas as pd
import hashlib

def hash_record(row: pd.Series, key_columns: list[str]) -> str:
    composite_key = "|".join(
        str(row[col]).strip().lower() if pd.notna(row[col]) else ""
        for col in key_columns
    )
    return hashlib.sha256(composite_key.encode()).hexdigest()

df = pd.read_parquet("s3://raw/orders/2026/04/03/batch_*.parquet")
KEY_COLS = ["order_id", "customer_id", "placed_at", "total_cents"]
df["dedup_hash"] = df.apply(hash_record, axis=1, key_columns=KEY_COLS)
deduped = df.drop_duplicates(subset=["dedup_hash"], keep="first")
print(f"Entfernt: {len(df) - len(deduped):,} Duplikate ({(len(df) - len(deduped)) / len(df):.1%})")

In SQL (DuckDB / BigQuery / Snowflake):

WITH ranked AS (
    SELECT *,
        ROW_NUMBER() OVER (
            PARTITION BY order_id, customer_id, DATE_TRUNC('second', placed_at)
            ORDER BY ingested_at ASC
        ) AS row_num
    FROM raw.orders
)
SELECT * EXCLUDE (row_num) FROM ranked WHERE row_num = 1;

Falle: Pass auf, was im Hash-Key steckt. ingested_at oder file_path machen jede Zeile unique — du dedupst nichts.

Strategie 2 — Fuzzy Matching

Für Duplikate, die dieselbe Entity meinen, aber durch Tippfehler, Abkürzungen oder Formatunterschiede abweichen. Klassisch: Namen und Adressen.

AlgorithmusMisstGut für
LevenshteinEdit-DistanzKurze Strings, Namen
Jaro-WinklerTranspositions-Gewichtung, Prefix-BonusNamen
Token Sort RatioWortreihenfolge-unabhängigAdressen, Firmen
Soundex / MetaphonePhonetische ÄhnlichkeitNamen ähnlich gesprochen
import pandas as pd
from thefuzz import fuzz

customers = pd.DataFrame({
    "customer_id": ["C001", "C002", "C003", "C004", "C005"],
    "name": ["John Smith", "Jon Smith", "Jonathan Smith", "Jane Doe", "J. Doe"],
    "email": ["john@acme.com", "jon@acme.com", "jonathan@acme.com", "jane@acme.com", "j.doe@acme.com"],
})

def find_fuzzy_duplicates(df: pd.DataFrame, name_col: str, threshold: int = 85) -> list[tuple]:
    duplicates = []
    names = df[name_col].tolist()
    for i in range(len(names)):
        for j in range(i + 1, len(names)):
            score = fuzz.token_sort_ratio(names[i], names[j])
            if score >= threshold:
                duplicates.append((df.index[i], df.index[j], score))
    return duplicates

pairs = find_fuzzy_duplicates(customers, "name", threshold=85)

Blocking für Skalierung: Jeden Record gegen jeden anderen zu vergleichen ist O(n²) — bricht jenseits ~100k zusammen. Mit Soundex-Blocks vorfiltern:

import jellyfish
customers["soundex_block"] = customers["name"].apply(
    lambda x: jellyfish.soundex(x.split()[0])
)
for block, group in customers.groupby("soundex_block"):
    if len(group) > 1:
        pairs = find_fuzzy_duplicates(group, "name", threshold=85)

Falle: Fuzzy Matching hat keine Ground Truth. Threshold 85 für Namen mergt vielleicht fälschlich "ACME Corp" und "ACME Labs". Immer mit Labeled Examples validieren.

Strategie 3 — Record Linkage (Entity Resolution)

Mergt Records aus verschiedenen Datasets, die dieselbe Real-World-Entity meinen — auch ohne gemeinsamen Key. Anwendungsfälle: CRM ↔ ERP, M&A-Konsolidierung, Survey ↔ Purchase-History.

import pandas as pd
import recordlinkage

crm = pd.DataFrame({
    "name": ["Alice Johnson", "Bob Martinez", "Carol White"],
    "email": ["alice@example.com", "bob@example.com", "carol@example.com"],
    "postcode": ["10115", "10117", "10119"],
})
erp = pd.DataFrame({
    "company_contact": ["A. Johnson", "Robert Martinez", "Carol White-Smith"],
    "contact_email": ["alice@example.com", "b.martinez@example.com", "carol@example.com"],
    "zip": ["10115", "10117", "10119"],
})

indexer = recordlinkage.Index()
indexer.block("postcode", "zip")
candidate_pairs = indexer.index(crm, erp)

comparer = recordlinkage.Compare()
comparer.string("name", "company_contact", method="jarowinkler", label="name_sim")
comparer.exact("email", "contact_email", label="email_exact")
comparer.exact("postcode", "zip", label="postcode_exact")

features = comparer.compute(candidate_pairs, crm, erp)
matches = features[features["email_exact"] == 1]

Für Large-Scale-Linkage skaliert Splink (UK Ministry of Justice) probabilistische Fellegi-Sunter-Modelle bis in hundert Millionen Records mit Spark.

Welche Strategie wann?

Duplikate aus Doppel-Ingestion / Retries?
  └─ JA → Hash-basiert

Duplikate aus Tippfehlern / Abkürzungen?
  └─ JA → Fuzzy Matching (mit Blocking)

Zwei Datasets ohne gemeinsamen Key?
  └─ JA → Record Linkage

Mix aus allem?
  └─ JA → Pipeline: erst Hash, dann Fuzzy, dann Linkage

Production-Pipeline

Raw Data
   ↓
[1] Hash-basiert (exakte Duplikate)
   ↓
[2] Standardisierung (lowercase, normalize)
   ↓
[3] Fuzzy Matching in Blocks
   ↓
[4] Human Review Queue (80–90 % Similarity)
   ↓
[5] Golden Record (Field-by-Field merge)
   ↓
Deduplicated Output

Die Human Review Queue ist bei sensiblen Daten nicht verhandelbar. 99 % Genauigkeit auf 1 Mio. Records sind immer noch 10.000 falsche Merges.

Häufige Fallen

  • Threshold ohne Labeled Data: Raten statt Messen. Bau ein Sample von ~500 Paaren (echte Matches + Non-Matches) und miss Precision/Recall.
  • Nicht normalisieren vor Vergleich: "ACME Inc." und "acme inc" haben Edit-Distanz > 0. Erst lowercase + Punctuation stripping.
  • Falsch mergen: Beim Golden Record Field-by-Field entscheiden, welche Quelle authoritativ ist. Nicht blind den neuesten Wert nehmen.
  • Temporale Duplikate ignorieren: Dasselbe Event darf legitim doppelt sein, wenn jemand zweimal gekauft hat. Timestamps + Business-Kontext nutzen.

FAQ

Wie viele Duplikate sind "normal"? Hängt vom Use Case ab. Marketing-CRMs liegen oft bei 5–15 %. Wenn dein Wert weit höher ist, liegt ein Ingestion-Problem vor.

Wann Splink, wann recordlinkage? recordlinkage für lokale Python-Workflows bis ~1 Mio. Records. Splink mit Spark für 10 Mio.+.

Reicht das DSGVO-konform? Solange du PII-Felder vor Vergleich nicht extern teilst und Logs maskierst, ja. Für Cross-Org-Linkage brauchst du Auftragsverarbeitungs-Vertrag.

Wie oft dedupen? Als Pipeline-Stage, nicht One-Off. Mit jedem neuen Batch neue Duplikate.

Stand: 14. Mai 2026.

H

Geschrieben von

Harbinger Team

Cloud-, Data- und AI-Engineer in DACH. Schreibt seit 2018 über infrastruktur­kritische Tech-Entscheidungen — keine Marketing- Folien, sondern echte Trade-offs aus Production-Workloads.

Hat dir das geholfen?

Jede Woche ein neuer Artikel über DACH-Cloud, Data und AI — direkt in dein Postfach. Kein Spam, kein Marketing-Sprech.

Kein Spam. 1-Klick-Abmeldung. Datenschutz bei Loops.so.