LX

SQL DISTINCT: Doppelte Zeilen entfernen, mit Beispielen

Vom LogicExcel-Redaktionsteam••16 Min. Lesezeit•3,032 Wörter

SQL DISTINCT: Doppelte Zeilen entfernen, mit Beispielen

Eine Datenanalystin überprüft eine SQL-Abfrage auf ihrem Laptop SELECT DISTINCT gibt jede Zeile Ihres Abfrageergebnisses nur einmal zurück. Kommt ein Wert oder eine Kombination von Werten mehrfach vor, bleibt genau ein Exemplar übrig. Das Schlüsselwort steht direkt hinter SELECT und bezieht sich immer auf alle Spalten, die Sie danach auswählen, nicht nur auf die erste. In diesem Artikel sehen Sie an einer kleinen Beispieltabelle, wie DISTINCT mit einer und mit mehreren Spalten arbeitet, was mit NULL passiert, wie Sie mit COUNT(DISTINCT ...) eindeutige Werte zählen und wann GROUP BY, WHERE oder HAVING die bessere Wahl ist.

TL;DR

  • SELECT DISTINCT spalte FROM tabelle; entfernt doppelte Zeilen aus dem Ergebnis, nicht aus der Tabelle.
  • Bei mehreren Spalten zählt die Kombination: Eine Zeile fällt nur weg, wenn alle ausgewählten Spalten mit einer anderen Zeile übereinstimmen.
  • COUNT(DISTINCT spalte) zählt verschiedene Werte und ignoriert dabei NULL; SELECT DISTINCT dagegen zeigt NULL einmal an.
  • DISTINCT filtert nur Duplikate. Sobald Sie Summen, Durchschnitte oder Anzahlen pro Gruppe brauchen, nehmen Sie GROUP BY.
  • WHERE filtert einzelne Zeilen vor dem Gruppieren, HAVING filtert Gruppen nach dem Gruppieren.

Inhaltsverzeichnis

Die Beispieltabelle

Alle Abfragen in diesem Artikel laufen auf dieser kleinen Tabelle bestellungen. Sie ist bewusst so gebaut, dass sie Duplikate und fehlende Werte enthält, genau wie echte Daten aus einem Shop oder CRM:

bestell_idkundestadtbetrag
1MüllerBerlin120
2SchmidtHamburg80
3MüllerBerlin45
4YilmazKöln200
5SchmidtMünchen60
6WeberNULL30
7BeckerNULL95
Sieben Bestellungen, fünf Kunden, vier bekannte Städte und zwei Bestellungen ohne Stadtangabe. Mit diesen Zahlen können Sie jedes Ergebnis unten selbst nachrechnen.

So funktioniert SELECT DISTINCT

Die Grundsyntax ist kurz:

SELECT DISTINCT spalte1, spalte2
FROM tabellenname;

DISTINCT wirkt auf das Ergebnis Ihrer Abfrage. Die Tabelle selbst bleibt unverändert, es wird nichts gelöscht. Wenn Sie Duplikate dauerhaft aus einer Tabelle entfernen wollen, ist das eine Aufgabe für DELETE, und die sollten Sie erst angehen, wenn Sie die doppelten Einträge sauber identifiziert haben (dazu weiter unten mehr).

Eine Spalte: die Liste der eindeutigen Werte

SELECT DISTINCT kunde
FROM bestellungen;

Ergebnis: Müller, Schmidt, Yilmaz, Weber, Becker. Aus sieben Zeilen werden fünf, weil Müller und Schmidt je zweimal bestellt haben. Das ist der klassische Einsatz: Sie wollen wissen, welche Werte in einer Spalte überhaupt vorkommen, etwa alle Kunden, alle Produktkategorien oder alle Länder, in die Sie liefern.

Eine Garantie für eine Reihenfolge gibt es dabei nicht. Wenn die Liste alphabetisch erscheinen soll, hängen Sie ORDER BY kunde an. Für absteigende Sortierung schreiben Sie ORDER BY kunde DESC.

Mehrere Spalten: Die Kombination entscheidet

Hier passieren die meisten Missverständnisse. DISTINCT gilt nicht für die erste Spalte allein, sondern für die ganze Zeile, die Sie auswählen:

SELECT DISTINCT kunde, stadt
FROM bestellungen;
kundestadt
MüllerBerlin
SchmidtHamburg
YilmazKöln
SchmidtMünchen
WeberNULL
BeckerNULL
Sechs Zeilen statt fünf. Müller taucht nur einmal auf, weil beide Bestellungen dieselbe Kombination „Müller, Berlin“ haben. Schmidt erscheint zweimal, weil „Schmidt, Hamburg“ und „Schmidt, München“ verschiedene Kombinationen sind. Die ClickHouse-Dokumentation zu DISTINCT beschreibt genau das: Von jeder Menge vollständig übereinstimmender Zeilen bleibt nur eine einzige im Ergebnis.

Daraus folgt eine praktische Regel: Jede zusätzliche Spalte nach DISTINCT kann die Zahl der Ergebniszeilen nur erhöhen oder gleich lassen, nie verringern. Nehmen Sie bestell_id mit auf, ist jede Zeile eindeutig und DISTINCT bewirkt gar nichts mehr.

DISTINCT und NULL-Werte

NULL bedeutet „Wert unbekannt“. Bei Vergleichen ist NULL eigentlich nie gleich NULL, aber DISTINCT macht hier eine Ausnahme und behandelt alle NULL-Werte als gleich. Laut der ClickHouse-Dokumentation arbeitet DISTINCT mit NULL so, als wäre NULL ein bestimmter Wert. Das verhalten die gängigen Datenbanken ebenso:

SELECT DISTINCT stadt
FROM bestellungen;

Ergebnis: Berlin, Hamburg, Köln, München und eine einzige Zeile NULL. Die zwei Bestellungen von Weber und Becker ohne Stadt fallen zu einer NULL-Zeile zusammen.

Wollen Sie NULL gar nicht in der Liste sehen, filtern Sie es vorher mit WHERE heraus:

SELECT DISTINCT stadt
FROM bestellungen
WHERE stadt IS NOT NULL;

Wichtig: WHERE stadt <> NULL funktioniert nicht, weil jeder Vergleich mit NULL unbekannt ergibt. Es muss IS NOT NULL heißen. Wenn Sie das an weiteren Fällen festigen möchten, finden Sie bei uns eine eigene Übung zu NULL-Werten in SQL.

COUNT(DISTINCT): eindeutige Werte zählen

Oft brauchen Sie nicht die Liste, sondern nur die Anzahl. Dafür kombinieren Sie DISTINCT mit COUNT:

SELECT
  COUNT(*)               AS bestellungen,
  COUNT(DISTINCT kunde)  AS kunden,
  COUNT(stadt)           AS mit_stadt,
  COUNT(DISTINCT stadt)  AS staedte
FROM bestellungen;
bestellungenkundenmit_stadtstaedte
7554
Die vier Zahlen zeigen die vier Varianten von COUNT:
  • COUNT(*) zählt alle Zeilen, auch solche mit NULL: 7.
  • COUNT(DISTINCT kunde) zählt verschiedene Kunden: 5.
  • COUNT(stadt) zählt Zeilen, in denen stadt nicht NULL ist: 5.
  • COUNT(DISTINCT stadt) zählt verschiedene Städte ohne NULL: 4.
Achten Sie auf den Unterschied zwischen Punkt 4 und der Abfrage im Abschnitt davor: SELECT DISTINCT stadt liefert fünf Zeilen (inklusive NULL), COUNT(DISTINCT stadt) liefert 4. Wer das eine mit dem anderen gegenprüft, wundert sich sonst über eine fehlende Zeile.

Wie groß der Unterschied in echten Daten sein kann, zeigt ein Beispiel aus der dbt-Dokumentation zu DISTINCT: Dort stehen 99 Bestellungen nur 62 verschiedenen Kunden gegenüber. Wer im Bericht „Kunden“ schreibt und COUNT(*) rechnet, liegt also schnell um mehr als die Hälfte daneben. Mehr Varianten zum Zählen üben Sie in unserer COUNT-Übung.

Noch ein Hinweis zur Portabilität: Einige Systeme erlauben COUNT(DISTINCT spalte1, spalte2) mit mehreren Spalten, andere nicht. Wenn Ihre Abfrage zwischen Datenbanken wandern soll, zählen Sie Kombinationen sicherer über eine Unterabfrage: SELECT COUNT(*) FROM (SELECT DISTINCT kunde, stadt FROM bestellungen) AS t; ergibt hier 6.

DISTINCT oder GROUP BY?

Für eine einfache Liste eindeutiger Werte liefern beide dasselbe:

SELECT DISTINCT kunde FROM bestellungen;

SELECT kunde FROM bestellungen GROUP BY kunde;

Das ist kein Zufall. Im MySQL-Referenzhandbuch zur DISTINCT-Optimierung heißt es, dass DISTINCT in den meisten Fällen als Sonderfall von GROUP BY betrachtet werden kann. Der Unterschied liegt im Zweck. DISTINCT entfernt Duplikate, mehr nicht. GROUP BY bildet Gruppen, damit Sie pro Gruppe rechnen können:

SELECT kunde, SUM(betrag) AS umsatz, COUNT(*) AS anzahl
FROM bestellungen
GROUP BY kunde;
kundeumsatzanzahl
Müller1652
Schmidt1402
Yilmaz2001
Weber301
Becker951
Mit DISTINCT allein ist diese Tabelle nicht zu bauen. Die Entscheidung im Überblick:
FrageDISTINCTGROUP BY
Welche Werte kommen vor?Ja, die direkteste SchreibweiseJa, funktioniert ebenso
Summe, Durchschnitt, Anzahl pro Wert?NeinJa, mit SUM, AVG, COUNT, MIN, MAX
Gruppen nach Ergebnis filtern?NeinJa, mit HAVING
Lesbarkeit für KollegenSignalisiert klar „nur eindeutige Zeilen“Signalisiert „hier wird aggregiert“
Unsere Faustregel: Schreiben Sie DISTINCT, wenn Sie eine eindeutige Liste wollen, und GROUP BY, sobald eine Aggregatfunktion ins Spiel kommt. Ein SELECT DISTINCT mit GROUP BY in derselben Abfrage ist fast immer überflüssig, weil GROUP BY die Zeilen bereits eindeutig macht. Wer MIN und MAX pro Gruppe sicher beherrschen will, findet dazu eine eigene Übung zu MIN und MAX.

WHERE oder HAVING: Wo gehört der Filter hin?

Sobald Sie gruppieren, stellt sich die nächste Frage: Filtern Sie mit WHERE oder mit HAVING? Die Antwort hängt davon ab, wann der Filter greifen soll. Microsoft beschreibt die HAVING-Klausel als eine Art WHERE-Klausel, die nur für Gruppen als Ganzes gilt. In einer viel gelesenen Diskussion auf Stack Overflow wird es auf den Punkt gebracht: WHERE prüft Bedingungen vor der Aggregation, HAVING danach.

WHERE filtert vor, HAVING nach der Gruppierung

Die Syntax von HAVING

SELECT spalte, AGGREGAT(spalte2)
FROM tabelle
WHERE bedingung_fuer_zeilen
GROUP BY spalte
HAVING bedingung_fuer_gruppen
ORDER BY spalte;

Die Reihenfolge der Klauseln ist fest. HAVING steht immer nach GROUP BY und vor ORDER BY.

Beide Filter in einer Abfrage

Beide Filter in einer Abfrage — overview diagram

Angenommen, Sie suchen Kunden mit mehr als 100 Euro Umsatz, wollen dabei aber Kleinstbestellungen unter 50 Euro nicht mitzählen:

SELECT kunde, SUM(betrag) AS umsatz
FROM bestellungen
WHERE betrag >= 50
GROUP BY kunde
HAVING SUM(betrag) > 100;

Schritt für Schritt:

  • WHERE betrag >= 50 wirft die Bestellungen 3 (45 Euro) und 6 (30 Euro) hinaus.
  • GROUP BY kunde bildet aus den übrigen fünf Zeilen die Gruppen Müller 120, Schmidt 140, Yilmaz 200, Becker 95.
  • HAVING SUM(betrag) > 100 behält Müller, Schmidt und Yilmaz. Becker fällt mit 95 Euro heraus.
Ohne den WHERE-Filter hätte Müller 165 Euro, das Ergebnis enthielte dieselben drei Kunden, aber mit anderen Summen. Genau das ist der Punkt: Die beiden Filter beantworten unterschiedliche Fragen.
KriteriumWHEREHAVING
WannVor dem GruppierenNach dem Gruppieren
WoraufEinzelne ZeilenGruppen
Aggregatfunktionen erlaubtNeinJa
Ohne GROUP BY sinnvollJa, ständigSelten, dann ist die ganze Tabelle eine Gruppe
Typisches BeispielWHERE stadt = 'Berlin'HAVING COUNT(*) > 1
Ein praktischer Tipp: Alles, was sich auf einzelne Zeilen bezieht, gehört in WHERE, auch wenn es in HAVING technisch funktionieren würde. So muss die Datenbank weniger Zeilen gruppieren, und Ihre Abfrage bleibt verständlich.

Wo DISTINCT in der Ausführung steht

SQL wird nicht in der Reihenfolge ausgeführt, in der Sie es schreiben. Die PostgreSQL-Dokumentation zu SELECT beschreibt die logische Abfolge: zuerst FROM, dann WHERE, GROUP BY und HAVING, danach werden die SELECT-Ausdrücke berechnet, anschließend greift DISTINCT, und erst dann folgen ORDER BY und LIMIT. Daraus ergeben sich drei Konsequenzen:

  • DISTINCT sieht nur Zeilen, die WHERE und HAVING überlebt haben.
  • DISTINCT arbeitet auf den berechneten Ausgabespalten, also etwa auf UPPER(kunde), wenn Sie das auswählen.
  • LIMIT 10 nach DISTINCT liefert zehn eindeutige Zeilen, nicht zehn Zeilen, aus denen dann Duplikate entfernt werden. Das Begrenzen üben Sie in unserer LIMIT-Übung.

DISTINCT ON in PostgreSQL

Manchmal wollen Sie pro Wert genau eine ganze Zeile, etwa die größte Bestellung je Kunde. Standard-DISTINCT hilft hier nicht, weil es alle ausgewählten Spalten vergleicht. PostgreSQL bietet dafür eine eigene Erweiterung, die die PostgreSQL-Dokumentation zu Select-Listen als SELECT DISTINCT ON (ausdruck) ... beschreibt:

SELECT DISTINCT ON (kunde) kunde, bestell_id, betrag
FROM bestellungen
ORDER BY kunde, betrag DESC;

Das Ergebnis enthält pro Kunde eine Zeile, und das ORDER BY bestimmt, welche: hier jeweils die mit dem höchsten Betrag, bei Müller also Bestellung 1 mit 120 Euro. Ohne passendes ORDER BY ist nicht vorhersagbar, welche Zeile gewinnt. DISTINCT ON ist keine Standard-SQL-Syntax. In anderen Datenbanken lösen Sie dieselbe Aufgabe meist mit einer Fensterfunktion wie ROW_NUMBER().

Leistung und typische Fehler

DISTINCT ist bequem, hat aber einen Preis. Um Duplikate zu erkennen, muss die Datenbank alle Ergebniszeilen miteinander abgleichen, meist durch Sortieren oder über eine Hash-Tabelle. Bei kleinen Tabellen merken Sie davon nichts. Bei Millionen Zeilen und vielen Spalten kann die Abfrage spürbar langsamer werden, besonders wenn der Arbeitsspeicher nicht reicht und Zwischenergebnisse auf die Festplatte ausgelagert werden müssen. Ein Index auf der betroffenen Spalte hilft der Datenbank oft, das Sortieren zu sparen.

Fehler 1: DISTINCT als Pflaster für einen falschen JOIN

Der häufigste Missbrauch: Eine Abfrage mit JOIN liefert plötzlich doppelte Zeilen, und jemand schreibt DISTINCT davor. Die Zahlen sehen danach ordentlich aus, aber das eigentliche Problem bleibt. Meist fehlt eine Bedingung im ON, oder eine Tabelle enthält mehrere Zeilen pro Schlüssel (etwa mehrere Adressen pro Kunde). Spätestens wenn Sie danach SUM(betrag) rechnen, stimmen die Summen nicht mehr, denn DISTINCT hilft bei der Summe nicht. Prüfen Sie zuerst die JOIN-Bedingung. Wie sich JOINs verhalten, können Sie in unseren Übungen zu JOIN und LEFT JOIN gefahrlos ausprobieren.

Fehler 2: DISTINCT gilt für alle Spalten

SELECT DISTINCT kunde, betrag liefert nicht „eindeutige Kunden mit ihrem Betrag“, sondern eindeutige Paare aus Kunde und Betrag. Wer pro Kunde einen Wert sehen will, braucht GROUP BY mit einer Aggregatfunktion oder DISTINCT ON.

Fehler 3: Klammern, die nichts bewirken

SELECT DISTINCT(kunde), stadt sieht aus, als würde DISTINCT nur auf kunde wirken. Die Klammern sind aber nur Klammern um einen Ausdruck. DISTINCT gilt trotzdem für kunde und stadt gemeinsam.

Fehler 4: Duplikate in der Quelle ignorieren

Wenn DISTINCT in jedem zweiten Bericht auftaucht, liegt das Problem meist vor der Abfrage: Daten werden doppelt importiert, Schreibweisen weichen ab („Köln“ und „Koeln“), Leerzeichen hängen am Ende. DISTINCT hält „Müller“ und „Müller “ für zwei verschiedene Kunden. Hier lohnt sich ein Blick auf die Datenpflege insgesamt; einen nüchternen Fahrplan, um die Datenqualität im Unternehmen zu verbessern, finden Sie dort für kleine und mittlere Betriebe. Kommen Ihre Daten aus Excel, entfernen Sie überflüssige Leerzeichen am besten schon vor dem Import, zum Beispiel mit der GLÄTTEN-Funktion in Excel.

Doppelte Einträge finden statt verstecken

DISTINCT blendet Duplikate aus. Wenn Sie wissen wollen, welche Einträge doppelt sind, drehen Sie die Logik um und kombinieren GROUP BY mit HAVING:

SELECT kunde, COUNT(*) AS anzahl
FROM bestellungen
GROUP BY kunde
HAVING COUNT(*) > 1;

Ergebnis: Müller mit 2 und Schmidt mit 2. Für echte Dubletten prüfen Sie alle Spalten, die einen Datensatz ausmachen sollten, zum Beispiel GROUP BY kunde, stadt, betrag. Erst wenn diese Liste stimmt, ist es sinnvoll, über das Löschen nachzudenken. Und noch ein verwandter Fall: UNION entfernt beim Zusammenführen zweier Abfragen Duplikate automatisch, UNION ALL behält sie. Das können Sie in unserer Übung zu UNION direkt vergleichen.

Unsere Sicht: DISTINCT lernt man durch Tippen

Wir sehen bei Lernenden immer wieder dasselbe Muster. Die Definition von DISTINCT ist in einer Minute verstanden, und trotzdem landet in der ersten echten Auswertung ein DISTINCT an der falschen Stelle: vor einem kaputten JOIN, vor einer Summe, die es nicht korrigieren kann, oder vor zwei Spalten, wo nur eine gemeint war. Das liegt nicht an fehlendem Wissen, sondern an fehlender Routine.

Deshalb setzen wir bei LogicExcel auf aktives Üben statt passives Lesen. Sie schreiben die Abfrage selbst, sehen sofort, ob das Ergebnis stimmt, und bekommen zu jeder Antwort eine kurze Erklärung. Keine Multiple-Choice-Fragen, bei denen man die richtige Antwort wiedererkennt, sondern echte Eingaben, bei denen man sie selbst finden muss. Gerade bei DISTINCT, GROUP BY und HAVING entsteht das Gefühl für die Ausführungsreihenfolge erst, wenn Sie dieselbe Tabelle fünfmal mit leicht veränderten Abfragen befragt haben.

Unser Rat für Analysten und alle, die ihre eigenen Berichte bauen: Formulieren Sie vor jeder Abfrage in einem Satz, was eine Zeile im Ergebnis bedeuten soll. „Eine Zeile pro Kunde“ führt zu GROUP BY oder DISTINCT auf genau einer Spalte. „Eine Zeile pro Bestellung“ braucht gar kein DISTINCT. Wer diesen Satz klar hat, braucht DISTINCT seltener und setzt es dann richtig ein.

> — Thomas Houza

SQL und Excel kostenlos im Browser üben

Auf LogicExcel üben Sie SQL-Befehle wie COUNT, JOIN, LIMIT, UNION oder den Umgang mit NULL direkt im Browser, mit Sofort-Feedback, kostenlos und ohne Anmeldung oder Installation. Wenn Ihre Daten zuerst durch Excel laufen, ergänzen unsere Excel-Kurse von den Grundlagen bis zu Lookups und Logik das Training um Bereinigung, Tabellen und Funktionen wie SVERWEIS und WENN.

Häufige Fragen zu DISTINCT

Bezieht sich DISTINCT nur auf die erste Spalte?

Nein. DISTINCT gilt immer für die gesamte Kombination aller Spalten, die Sie nach SELECT auswählen. Eine Zeile fällt nur weg, wenn sie in jeder ausgewählten Spalte mit einer anderen übereinstimmt. Wer pro Wert einer einzelnen Spalte genau eine Zeile will, nutzt GROUP BY oder in PostgreSQL DISTINCT ON.

Ist GROUP BY schneller als DISTINCT?

Für eine reine Liste eindeutiger Werte erzeugen viele Datenbanken intern einen sehr ähnlichen Ausführungsplan, sodass es kaum Unterschiede gibt. Entscheidend ist meist ein passender Index, nicht das Schlüsselwort. Wählen Sie das, was Ihre Absicht klarer ausdrückt, und prüfen Sie bei großen Tabellen den Plan mit EXPLAIN.

Kann ich HAVING ohne GROUP BY verwenden?

Ja, dann behandelt die Datenbank die gesamte Ergebnismenge als eine einzige Gruppe. SELECT COUNT() FROM bestellungen HAVING COUNT() > 5; liefert eine Zeile, wenn die Bedingung erfüllt ist, sonst keine. In der Praxis kommt das selten vor, meist steht HAVING zusammen mit GROUP BY.

Warum zählt COUNT(DISTINCT) weniger Werte, als SELECT DISTINCT Zeilen zeigt?

Weil COUNT NULL-Werte nie mitzählt, SELECT DISTINCT eine NULL-Zeile aber anzeigt. In unserem Beispiel liefert SELECT DISTINCT stadt fünf Zeilen, COUNT(DISTINCT stadt) aber 4. Wer beide Zahlen vergleicht, sollte das wissen, bevor er einen Fehler sucht.

Löscht DISTINCT doppelte Datensätze aus der Tabelle?

Nein. DISTINCT verändert nur das Ergebnis einer Abfrage, die gespeicherten Daten bleiben unangetastet. Um Duplikate dauerhaft zu entfernen, finden Sie sie zuerst mit GROUP BY und HAVING COUNT(*) > 1 und löschen sie dann gezielt mit DELETE, idealerweise nach einer Sicherung.

Quellen

Empfehlungen

Weiterlernen

Anleitungen, die andere als Nächstes lesen.