SELECT PERSONEN.SID_PERSONEN
FROM PERSONEN
WHERE EXISTS (
SELECT 'Results!'
FROM PERSONEN Y
WHERE PERSONEN.SID_PERSONEN <> Y.SID_PERSONEN
AND (ISNULL(SOUNDEX(PERSONEN.NAME), '') = ISNULL(SOUNDEX(Y.NAME), '') AND PERSONEN.NAME IS NOT NULL AND LEN(RTRIM(PERSONEN.NAME)) > 0)
AND (ISNULL(SOUNDEX(PERSONEN.VORNAME), '') = ISNULL(SOUNDEX(Y.VORNAME), '') AND PERSONEN.VORNAME IS NOT NULL AND LEN(RTRIM(PERSONEN.VORNAME)) > 0)
AND (ISNULL(PERSONEN.POSTPLZZ, '') = ISNULL(Y.POSTPLZZ, ''))
AND (ISNULL(SOUNDEX(PERSONEN.POSTSTRASSE), '') = ISNULL(SOUNDEX(Y.POSTSTRASSE), ''))
AND (ISNULL(PERSONEN.DUB_OK, '') = ISNULL(Y.DUB_OK, ''))
AND (ISNULL(PERSONEN.EMAIL1, '') = ISNULL(Y.EMAIL1, '') AND PERSONEN.EMAIL1 IS NOT NULL AND LEN(RTRIM(PERSONEN.EMAIL1)) > 0))
Ich finde das Thema Dubletten ganz interessant aus Datenbank-Sicht. Das SQL scheint mir hier eher suboptimal. Zum einen, weil durch SOUNDEX() das Recordset ja durchaus größer wird, als bei einem exakten Vergleich. Im Gegenzug wird es durch den Ausschluss von z.B. VORNAME IS NULL wieder rum kleiner. Wenn ich einen Michael Meyer habe und einen Meyer, dann könnten das ja auch die selben Personen sein. Jetzt könnte man natürlich sagen, Meyer gibt es halt oft, ist erstmal unwahrscheinlich. Aber es fließen ja einige Spalten in die Bewertung ein, z.B. auch die Postadresse. Die hätte ich persönlich eher raus gelassen aber wo sie schonmal drin ist, erscheint es mir gar nicht so unwahrscheinlich, das Michael Meyer und Meyer die selbe Person sind. Und SOUNDEX() zeigt ja, das man das eher weit gefasst betrachten will.
Ich würde daher überlegen, wo ich hin will. Will ich möglichst viele, potenzielle Dubletten aufspüren, muss ich NULL-Werte tendenziell eher auch berücksichtigen. Ansonsten wäre mir SOUNDEX() zu weit gefasst. Wenn es z.B. nur darum geht ss und ß gleich zu setzen, dann macht SQL das mit der richtigen COLLATION ggf. von allein bzw. man kann die COLLATION noch im SELECT bestimmen - kurz: es gibt andere Möglichkeiten.
Auch wird es Situationen geben, da gibt es eine Person wirklich „doppelt“ aus DB-Sicht. Und man hat vielleicht das entscheidende Attribut nicht, was die Personen unterscheidet z.B. Geburtsdatum). Für diesen Fall sollte man eine Markierung oder einen anderen Mechanismus vorsehen, der diese Fälle nach einer Prüfung fest hält und von der Dubletten-Suche ausnimmt.
Praktisch würde schon ein BIT reichen, das an einem von zwei Datensätzen gesetzt ist. Die Prüfung erfolgt dann auf „Beide Datensätze haben BIT nicht gesetzt“ und beide Datensätze fallen aus der Suche. Kommt ein dritter hinzu, gibt es zumindest wieder zwei Dubletten, also Datensatz A und C weil B markiert ist. Keine optimale aber eine einfache Lösung. (Sofern nicht z.B. Datensatz A einer Löschung unterworfen werden kann, dann muss man vielleicht umdenken weil B und C nicht gematcht werden.)
Auf die Überlegung gekommen bin ich überhaupt erst durch den Versuch, die Performance zu verbessern. Grundsätzlich würde ich @Björn Eggstein immer zustimmen, diese Abfrage hat auf einer Übersichtsseite nichts verloren - weniger ist mehr. Zumal die draus gewonnene Information vermutlich nur in ganz bestimmten Situationen gebracht werden. Dennoch ist bei der Performance deutlich mehr drin.
Der Ansatz mit EXISTS() ist ja durchaus logisch und wird auch auf alten SQL Systemen fast immer unterstützt. Manchmal lohnt es sich aber auch, Features wie z.B. Window-Funktionen in SQL zu nutzen. Die kann MSSQL auch schon lange und die sind ein mächtiges Werkzeug. Ich würde daher eine Variante ins Spiel bringen, die sich auch durch recht schlanken Code auszeichnet (pro Spalte) und deutlich schneller läuft bei mir.
SELECT PERSONEN.SID_PERSONEN
FROM PERSONEN
WHERE EXISTS (
SELECT 'Results!'
FROM PERSONEN Y
WHERE PERSONEN.SID_PERSONEN <> Y.SID_PERSONEN
AND (ISNULL(SOUNDEX(PERSONEN.NAME), '') = ISNULL(SOUNDEX(Y.NAME), '') AND PERSONEN.NAME IS NOT NULL AND LEN(RTRIM(PERSONEN.NAME)) > 0)
AND (ISNULL(SOUNDEX(PERSONEN.VORNAME), '') = ISNULL(SOUNDEX(Y.VORNAME), '') AND PERSONEN.VORNAME IS NOT NULL AND LEN(RTRIM(PERSONEN.VORNAME)) > 0)
AND (ISNULL(PERSONEN.ANREDE, '') = ISNULL(Y.ANREDE, '') AND PERSONEN.ANREDE IS NOT NULL AND LEN(RTRIM(PERSONEN.ANREDE)) > 0));
8.288 potenzielle Dubletten von 31.162 Datensätzen, Laufzeit 272 ms.
SELECT SID_PERSONEN
FROM (
SELECT *, count(*)
OVER (PARTITION BY soundex(NAME),soundex(VORNAME),ANREDE) AS x
FROM PERSONEN
) xx
WHERE xx.x > 1;
8.386 potenzielle Dubletten von 31.162 Datensätzen, Laufzeit 104 ms.
Die Differenz in den Treffern erklärt sich durch die Behandlung von NULL-Werten, bei mir gibt es da durchaus den ein oder Anderen. Ich habe darüber gegrübelt, wie man das 1:1 hin bekommt, bin dann aber allgemein damit nicht glücklich geworden und denke auch, dass das erstmal nicht ausschlaggebend ist.
Ich habe noch eine log-Tabelle nach dem gleichen Prinzip getestet, weil ich mehr Datensätze wollte. Mit den Spalten, die ich verglichen habe, gab es dabei unsinnig viele Dubletten aber die Laufzeit dürfte sich vor allem auf die Anzahl der Datensätze allgemein beziehen.
Variante mit EXISTS(): 88.646 potenzielle Dubletten von 88.648 Datensätzen, Laufzeit 2401 ms.
Variante mit Window-Funktion: 88.646 potenzielle Dubletten von 88.648 Datensätzen, Laufzeit 673 ms.
Ich denke, die Laufzeit spricht für sich.