CREATE TABLE tbl_Personen([Personen-Id] INT IDENTITY(1, 1) PRIMARY KEY, Nachname VARCHAR(50), Vorname VARCHAR(50), Geburtstag DATETIME) INSERT INTO tbl_Personen( Nachname, Vorname, Geburtstag) SELECT 'Schmidt', 'Hans', '1.1.1950' UNION SELECT 'Schmidt', 'Hans', '1.1.1950' UNION SELECT 'Schmidt', 'Hans', '1.1.1950' UNION SELECT 'Maier', 'Franz', NULL UNION SELECT 'Maier', 'Franz', NULL UNION SELECT 'Schubert', 'Gerd', '1.7.1960'Kopieren Sie sich den Code in eine Datei oder speichern Sie sich, falls Sie den MS-Sqlserver verwenden, die Datei Doppelte-Daten-Zeilen-loeschen.sql. Das Script erstellt zusätzlich eine gespeicherte Prozedur 'up_zeige_Zeilen_zum_Loeschen', welche die unten erläuterte SELECT-Anweisung enthält.
SELECT A.Nachname, A.Vorname, A.Geburtstag FROM tbl_Personen As A GROUP BY A.Nachname, A.Vorname, A.Geburtstag HAVING COUNT(*) > 1
SELECT A.[Personen-Id] As [Datensatz-ist-Kandidat-zum-Loeschen], A.Nachname, A.Vorname, A.Geburtstag FROM tbl_Personen As A Where A.[Personen-Id] NOT IN (SELECT MIN(X.[Personen-Id]) As [Datensatz-ist-einmalig] FROM tbl_Personen As X GROUP BY X.Nachname, X.Vorname, X.Geburtstag HAVING COUNT(*) = 1)Diese Abfrage dürfen Sie jedoch nicht zum tatsächlichen Löschen verwenden. Denn dann würden sämtliche mehrfach auftretenden Datensätze gelöscht, anstatt daß genau ein Datensatz erhalten bleibt. Beachten Sie, daß es aufgrund der Gruppierung notwendig ist, über X.[Personen-Id] zu aggregieren. Da die HAVING-Klausel ohnehin nur einen Datensatz liefert, ist die Wahl der Aggregatfunktion eher willkürlich.
SELECT A.[Personen-Id] As [Datensatz-ist-Kandidat-zum-Loeschen] From tbl_Personen As A INNER JOIN (Select X.Nachname, X.Vorname, X.Geburtstag From tbl_Personen As X Group By X.Nachname, X.Vorname, X.Geburtstag Having Count(*) > 1) As B On A.Nachname = B.Nachname And A.Vorname = B.Vorname And A.Geburtstag = B.GeburtstagDie Unterabfrage ermittelt alle Mengen mehrfach vorkommender Zeilen. Mit dem INNER JOIN wird diese reduzierte Tabelle mit der Haupttabelle verknüpft, so daß von dieser alle zu den mehrfach vorkommenden Zeilen gehörenden ID's aufgelistet werden.
Nur: Diese Lösung funktioniert nur dann, falls alle Zellen belegt sind. Gibt es mehrfache Zeilen, bei welchen eine Zelle leer ist, so werden diese durch den INNER JOIN ausgeschlossen. Sie sehen diesen Effekt in der Beispieltabelle, da nur die ID's 1 - 3 ausgegeben werden, der Datensatz 'Maier', 'Franz', NULL jedoch fehlt.
Verwendet man stattdessen den LEFT JOIN, so werden auch alle Zeilen mit hinzugenommen, für die es in der rechten Zeile keinen übereinstimmenden Datensatz gibt, insbesondere auch all jene Zeilen, die nur einmal vorkommen. Die Version mit LEFT JOIN zeigt Ihnen also alle Datensätze von tbl_Personen an, durch den LEFT JOIN wurde die Filterung wieder aufgehoben.
SELECT MIN(A.[Personen-Id]) As [Datensatz-soll-erhalten-bleiben] FROM tbl_Personen As A GROUP BY A.Nachname, A.Vorname, A.GeburtstagHier könnte man eine Unterabfrage einfügen, so daß nur jene Zeilen berücksichtigt werden, die tatsächlich mehrfach existieren. Die Verknüpfung mit der äußeren Tabelle würde jedoch erneut das oben erwähnte Problem mit den leeren Zellen erzeugen, so daß der folgende LEFT JOIN dasselbe Ergebnis liefert wie die sehr viel kürzere obige Abfrage:
SELECT MIN(A.[Personen-Id]) As [Datensatz-soll-erhalten-bleiben] FROM tbl_Personen As A LEFT JOIN (SELECT X.Nachname, X.Vorname, X.Geburtstag FROM tbl_Personen As X GROUP BY X.Nachname, X.Vorname, X.Geburtstag HAVING COUNT(*) > 1) As B ON A.Nachname = B.Nachname AND A.Vorname = B.Vorname AND A.Geburtstag = B.Geburtstag GROUP BY A.Nachname, A.Vorname, A.Geburtstag
SELECT A.[Personen-Id], A.Nachname, A.Vorname, A.Geburtstag FROM tbl_Personen As A WHERE [Personen-Id] IN (SELECT A.[Personen-Id] As [Datensatz-ist-Kandidat-zum-Loeschen] From tbl_Personen As A INNER JOIN (Select X.Nachname, X.Vorname, X.Geburtstag From tbl_Personen As X Group By X.Nachname, X.Vorname, X.Geburtstag Having Count(*) > 1) As B On A.Nachname = B.Nachname And A.Vorname = B.Vorname And A.Geburtstag = B.Geburtstag) And [Personen-Id] Not IN (SELECT Min(A.[Personen-Id]) As [Datensatz-soll-erhalten-bleiben] From tbl_Personen As A INNER Join (Select X.Nachname, X.Vorname, X.Geburtstag From tbl_Personen As X Group By X.Nachname, X.Vorname, X.Geburtstag Having Count(*) > 1) As B On A.Nachname = B.Nachname And A.Vorname = B.Vorname And A.Geburtstag = B.Geburtstag Group By A.Nachname, A.Vorname, A.Geburtstag)Wie Sie an den Beispieldaten sehen, funktioniert diese Lösung nicht mehr, falls Zellen leer sind. Es werden nur die beiden Zeilen für 'Schmidt', 'Hans', '1.1.1950' mit den ID's 2 und 3 zurückgegeben, es fehlt die ebenfalls zu löschende ID 5 mit 'Maier', 'Franz', NULL.
Ein Wechsel zum LEFT JOIN liefert Ihnen für die einschließende Unterabfrage alle Zeilen zurück, dieser Teil kann entfernt werden. Die zweite Unterabfrage liefert - mit LEFT JOIN geschrieben - auch die ID's zurück, die zu nur einmaligen Zeilen gehören. Also kann diese Unterabfrage vereinfacht werden, indem sofort über die ganze Tabelle gruppiert und pro Datenzeile das Minimum ermittelt wird:
SELECT A.[Personen-Id] As [Datensatz-ist-Kandidat-zum-Loeschen], A.Nachname, A.Vorname, A.Geburtstag -- alternativ: DELETE FROM tbl_Personen As A Where A.[Personen-Id] NOT IN (SELECT MIN(X.[Personen-Id]) FROM tbl_Personen As X GROUP BY X.Nachname, X.Vorname, X.Geburtstag)Vergleichen Sie diese Lösung mit den vorherigen Beispielen, so entspricht dies dem zweiten Codebeispiel, es wurde lediglich die HAVING - Klausel entfernt.
Für die tatsächliche Löschabfrage ersetzen Sie die SELECT-Zeile durch DELETE und entfernen As A sowie das spätere Präfix A. Bei DELETE-Befehlen ist kein Präfix erlaubt. Das Ergebnis:
DELETE FROM tbl_Personen Where [Personen-Id] NOT IN (SELECT MIN(X.[Personen-Id]) FROM tbl_Personen As X GROUP BY X.Nachname, X.Vorname, X.Geburtstag)
Das Sql-Script können Sie bei einer vertrauten Verbindung und lokalem Server mit dem folgenden Dos-Befehl ausführen:
OSQL -S (local) -E -i Doppelte-Daten-Zeilen-loeschen.sql -d Ihre_DatenbankErstellen Sie zuvor eine Datenbank 'Ihre_Datenbank', die fernab von Ihren Produktions-Datenbanken ist. Das Script zeigt zum Schluß zunächst die Tabelle an und führt dann einmal die gespeicherte Prozedur aus, so daß die zu löschenden Datensätze mit den ID 2, 3 und 5 angezeigt werden.