SQL DISTINCT: Doppelte Zeilen entfernen, mit Beispielen
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
- So funktioniert SELECT DISTINCT
- DISTINCT und NULL-Werte
- COUNT(DISTINCT): eindeutige Werte zählen
- DISTINCT oder GROUP BY?
- WHERE oder HAVING: Wo gehört der Filter hin?
- DISTINCT ON in PostgreSQL
- Leistung und typische Fehler
- Doppelte Einträge finden statt verstecken
- Unsere Sicht: DISTINCT lernt man durch Tippen
- SQL und Excel kostenlos im Browser üben
- Häufige Fragen zu DISTINCT
- Quellen
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_id | kunde | stadt | betrag |
| 1 | Müller | Berlin | 120 |
| 2 | Schmidt | Hamburg | 80 |
| 3 | Müller | Berlin | 45 |
| 4 | Yilmaz | Köln | 200 |
| 5 | Schmidt | München | 60 |
| 6 | Weber | NULL | 30 |
| 7 | Becker | NULL | 95 |
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;
| kunde | stadt |
| Müller | Berlin |
| Schmidt | Hamburg |
| Yilmaz | Köln |
| Schmidt | München |
| Weber | NULL |
| Becker | NULL |
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;
| bestellungen | kunden | mit_stadt | staedte |
| 7 | 5 | 5 | 4 |
- 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.
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;
| kunde | umsatz | anzahl |
| Müller | 165 | 2 |
| Schmidt | 140 | 2 |
| Yilmaz | 200 | 1 |
| Weber | 30 | 1 |
| Becker | 95 | 1 |
| Frage | DISTINCT | GROUP BY |
| Welche Werte kommen vor? | Ja, die direkteste Schreibweise | Ja, funktioniert ebenso |
| Summe, Durchschnitt, Anzahl pro Wert? | Nein | Ja, mit SUM, AVG, COUNT, MIN, MAX |
| Gruppen nach Ergebnis filtern? | Nein | Ja, mit HAVING |
| Lesbarkeit für Kollegen | Signalisiert klar „nur eindeutige Zeilen“ | Signalisiert „hier wird aggregiert“ |
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.
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
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.
| Kriterium | WHERE | HAVING |
| Wann | Vor dem Gruppieren | Nach dem Gruppieren |
| Worauf | Einzelne Zeilen | Gruppen |
| Aggregatfunktionen erlaubt | Nein | Ja |
| Ohne GROUP BY sinnvoll | Ja, ständig | Selten, dann ist die ganze Tabelle eine Gruppe |
| Typisches Beispiel | WHERE stadt = 'Berlin' | HAVING COUNT(*) > 1 |
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
- ClickHouse-Dokumentation: DISTINCT
- dbt-Dokumentation: Working with the SQL DISTINCT
- MySQL 8.4 Reference Manual: DISTINCT Optimization
- Microsoft Learn: HAVING- und WHERE-Klauseln in derselben Abfrage
- Stack Overflow: What is the difference between HAVING and WHERE?
- PostgreSQL-Dokumentation: SELECT
- PostgreSQL-Dokumentation: Select Lists und DISTINCT ON
- Datenqualität verbessern: Praxis-Fahrplan für KMU


