Um Duplikate in SQL zu finden, gruppierst du die Zeilen nach den Spalten, die eindeutig sein sollten, und behältst die Gruppen mit mehr als einer Zeile. Mit Window Functions und Self-Joins siehst du dann die doppelten Zeilen selbst und kannst entscheiden, welche Kopie bleibt. Die Beispiele funktionieren in PostgreSQL, MySQL, SQLite und SQL Server.
So findest du Duplikate in SQL
GROUP BY fasst Zeilen mit gleichem Wert zusammen, COUNT(*) zählt jede Gruppe, und HAVING behält die Gruppen mit mehr als einer Zeile:
SELECT columns, COUNT(*)
FROM table_name
GROUP BY columns
HAVING COUNT(*) > 1;
Zum Beispiel, um E-Mail-Adressen zu finden, die von mehr als einem Kunden verwendet werden:
-- Input: customers
-- id | name | email
-- ---+----------------+----------------------
-- 1 | Maria Lopez | maria.lopez@gmail.com
-- 2 | Sam Patel | sam@patel.dev
-- 3 | M. Lopez | maria.lopez@gmail.com
-- 4 | Kenji Watanabe | kenji@watanabe.jp
SELECT email, COUNT(*) AS copies
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;
-- Output:
-- email | copies
-- ----------------------+-------
-- maria.lopez@gmail.com | 2
Sollen Zeilen nur dann als Duplikate gelten, wenn mehrere Spalten übereinstimmen, führ jede davon sowohl in SELECT als auch in GROUP BY auf.
Wichtige Regeln
-
Jede ausgewählte Spalte muss in
GROUP BYstehen oder in einer Aggregatfunktion. Wenn dunamezur obigen Abfrage hinzufügst, siehst du nicht die Namen der Duplikate. Es ist ein Fehler, außer in SQLite, das einen Namen pro E-Mail anzeigt:
-- Input: the customers table above
SELECT name, email, COUNT(*) AS copies
FROM customers
GROUP BY email
HAVING COUNT(*) > 1;
-- ERROR: column "customers.name" must appear in the GROUP BY clause or be used in an aggregate function
Um jede doppelte Zeile zu sehen, nutze eine der Abfragen unter „Alternative Syntax“.
-
NULLs landen in einer Gruppe, und Groß-/Kleinschreibung zählt. Alle NULL-E-Mails kommen in eine Gruppe und wirken daher wie Duplikate voneinander, während
Ana@Silva.devundana@silva.deves nicht tun:
-- Input: users
-- id | email
-- ---+--------------
-- 1 | ana@silva.dev
-- 2 | NULL
-- 3 | Ana@Silva.dev
-- 4 | NULL
SELECT email, COUNT(*) AS copies
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
-- Output:
-- email | copies
-- ------+-------
-- NULL | 2
-- MySQL and SQL Server ignore case by default, so they also return ana@silva.dev | 2
-- Skip the NULLs and ignore case:
SELECT LOWER(email) AS email, COUNT(*) AS copies
FROM users
WHERE email IS NOT NULL
GROUP BY LOWER(email)
HAVING COUNT(*) > 1;
-- Output:
-- email | copies
-- --------------+-------
-- ana@silva.dev | 2
Alternative Syntax
GROUP BY liefert eine Zeile pro doppeltem Wert. Um die doppelten Zeilen selbst zu bekommen, zählst du stattdessen über ein Window. COUNT(*) OVER (PARTITION BY email) schreibt die Anzahl jeder E-Mail in jede Zeile, die sie enthält:
-- Input: the customers table above
SELECT id, name, email, copies
FROM (
SELECT id, name, email, COUNT(*) OVER (PARTITION BY email) AS copies
FROM customers
) AS c
WHERE copies > 1
ORDER BY id;
-- Output:
-- id | name | email | copies
-- ---+-------------+-----------------------+-------
-- 1 | Maria Lopez | maria.lopez@gmail.com | 2
-- 3 | M. Lopez | maria.lopez@gmail.com | 2
EXISTS macht dasselbe ohne Window Function: Behalte jede Zeile, für die eine andere Zeile mit derselben E-Mail existiert:
-- Input: the customers table above
SELECT id, name, email
FROM customers c
WHERE EXISTS (
SELECT 1 FROM customers other
WHERE other.email = c.email AND other.id <> c.id
);
-- Output:
-- id | name | email
-- ---+-------------+----------------------
-- 1 | Maria Lopez | maria.lopez@gmail.com
-- 3 | M. Lopez | maria.lopez@gmail.com
Wann solltest du nach Duplikaten suchen?
Duplikate entstehen durch Importe, Doppelklicks, Wiederholungsversuche und fehlende Constraints. Das sind die vier Aufgaben, die du dabei am häufigsten erledigen musst.
Prüfen, ob eine Spalte eindeutig ist
Bevor du einen UNIQUE-Constraint hinzufügst oder wenn ein Login oder Lookup plötzlich zwei Zeilen liefert, liste die Werte auf, die mehrfach vorkommen. GROUP BY mit HAVING beantwortet das direkt.
Alle betroffenen Zeilen anzeigen
Zu wissen, welcher Wert sich wiederholt, reicht zum Beheben nicht: Du brauchst die vollständigen Zeilen, um zu sehen, wie sie sich unterscheiden und welche richtig ist. Gib die doppelten Werte zurück in eine Abfrage auf dieselbe Tabelle.
Überzählige Kopien markieren
Zum Aufräumen behältst du eine Zeile aus jeder Gruppe von Duplikaten und entfernst den Rest. ROW_NUMBER() nummeriert die Zeilen innerhalb jeder Gruppe, also ist alles nach Zeile 1 eine überzählige Kopie.
Doppelte Übermittlungen erkennen
Ein doppelt geklickter Button oder eine wiederholte Zahlung hinterlässt zwei Zeilen, die bis auf die ID übereinstimmen. Ein Join der Tabelle mit sich selbst bildet Paare, und t1.id < t2.id listet jedes Paar nur einmal.
Praxisbeispiele zum Finden von Duplikaten
Doppelt gebuchte Hotelzimmer finden
Ein Zimmer kann pro Nacht nur einmal gebucht werden, ein Duplikat ist also dasselbe Zimmer in derselben Nacht. Gruppiere nach beiden Spalten:
-- Input: bookings
-- booking_id | room | night | guest
-- -----------+------+------------+----------
-- 1 | 204 | 2026-11-06 | R. Okafor
-- 2 | 204 | 2026-11-07 | R. Okafor
-- 3 | 310 | 2026-11-06 | L. Chen
-- 4 | 204 | 2026-11-06 | P. Novak
SELECT room, night, COUNT(*) AS bookings
FROM bookings
GROUP BY room, night
HAVING COUNT(*) > 1;
-- Output:
-- room | night | bookings
-- -----+------------+---------
-- 204 | 2026-11-06 | 2
Konten mit gemeinsamer E-Mail-Adresse auflisten
Leute registrieren sich zweimal mit derselben Adresse in unterschiedlicher Schreibweise. Partitioniere nach LOWER(email), um beide Konten nebeneinander zu sehen:
-- Input: accounts
-- id | username | email
-- ---+----------+----------------
-- 1 | jkim | jin@kim.kr
-- 2 | olivia_r | olivia@reyes.mx
-- 3 | jin.kim | Jin@Kim.kr
-- 4 | noah_b | noah@baker.uk
SELECT id, username, email, accounts_with_email
FROM (
SELECT id, username, email,
COUNT(*) OVER (PARTITION BY LOWER(email)) AS accounts_with_email
FROM accounts
) AS a
WHERE accounts_with_email > 1
ORDER BY id;
-- Output:
-- id | username | email | accounts_with_email
-- ---+----------+------------+--------------------
-- 1 | jkim | jin@kim.kr | 2
-- 3 | jin.kim | Jin@Kim.kr | 2
Doppelte Personen vor dem Aufräumen markieren
Behandle Nutzer mit gleichem Vor- und Nachnamen als eine Person, behalte den ältesten Datensatz und kennzeichne die anderen. Eine CTE trennt die Nummerierung von der Kennzeichnung:
-- Input: users
-- id | first_name | last_name | email
-- ---+------------+-----------+----------------
-- 1 | Priya | Shah | priya@shah.net
-- 2 | Tom | Becker | tom@becker.de
-- 3 | Priya | Shah | pshah@work.com
-- 4 | Lena | Kowalski | lena@kowal.pl
-- 5 | Tom | Becker | tbecker@mail.de
WITH numbered AS (
SELECT id, first_name, last_name, email,
ROW_NUMBER() OVER (PARTITION BY first_name, last_name ORDER BY id) AS copy_number
FROM users
)
SELECT id, first_name, last_name, email,
CASE WHEN copy_number > 1 THEN 'Duplicate' ELSE 'Original' END AS status
FROM numbered
ORDER BY id;
-- Output:
-- id | first_name | last_name | email | status
-- ---+------------+-----------+-----------------+----------
-- 1 | Priya | Shah | priya@shah.net | Original
-- 2 | Tom | Becker | tom@becker.de | Original
-- 3 | Priya | Shah | pshah@work.com | Duplicate
-- 4 | Lena | Kowalski | lena@kowal.pl | Original
-- 5 | Tom | Becker | tbecker@mail.de | Duplicate
Die mit Duplicate markierten Zeilen sind die, die du zusammenführen oder löschen solltest. Wie du sie löschst, zeigt der Guide zum Entfernen von Duplikaten.
Versehentlich doppelt aufgegebene Bestellungen finden
Zwei Bestellungen desselben Kunden für dasselbe Produkt am selben Tag sind wahrscheinlich eine doppelt abgeschickte Bestellung:
-- Input: orders
-- order_id | customer_id | product | order_date
-- ---------+-------------+-----------+-----------
-- 1001 | 42 | Desk Lamp | 2026-03-02
-- 1002 | 42 | Desk Lamp | 2026-03-02
-- 1003 | 42 | Desk Lamp | 2026-03-09
-- 1004 | 57 | Desk Lamp | 2026-03-02
SELECT o1.order_id AS first_order, o2.order_id AS repeat_order,
o1.customer_id, o1.product
FROM orders o1
JOIN orders o2
ON o1.customer_id = o2.customer_id
AND o1.product = o2.product
AND o1.order_date = o2.order_date
AND o1.order_id < o2.order_id;
-- Output:
-- first_order | repeat_order | customer_id | product
-- ------------+--------------+-------------+----------
-- 1001 | 1002 | 42 | Desk Lamp
Weiterführende Artikel zu Duplikaten
In diesem Blog:
- SQL-Guide: Duplikate entfernen: lösche die überzähligen Kopien, sobald du sie gefunden hast
- Doppelte Zeilen in einer SQL Server-Tabelle erkennen: dasselbe Problem in T-SQL
- SQL COUNT DISTINCT Guide: Werte ohne Duplikate zählen
-
SQL GROUP BY Guide: Gruppierung und
HAVINGim Detail -
Having vs. Where in SQL: warum der Filter auf die Anzahl in
HAVINGgehört - Unique Constraints in PostgreSQL erstellen: verhindere neue Duplikate
- SQL CTE vs. Subquery Guide: die zwei Wege, diese Abfragen zu strukturieren
Offizielle Dokumentation:
- PostgreSQL: GROUP BY und HAVING, Window Functions
- MySQL: SELECT, inklusive GROUP BY und HAVING, ROW_NUMBER()
- SQLite: SELECT, inklusive GROUP BY und HAVING, Window Functions
- SQL Server: HAVING, ROW_NUMBER
Beekeeper Studio ist eine kostenlose & quelloffene Datenbank-GUI
Das beste SQL-Abfrage- & Editor-Tool, das ich je benutzt habe. Es bietet alles, was ich zum Verwalten meiner Datenbank brauche. - ⭐⭐⭐⭐⭐ Mit
Beekeeper Studio ist schnell, intuitiv und einfach zu bedienen. Beekeeper unterstützt jede Menge Datenbanken und läuft hervorragend auf Windows, Mac und Linux.
Was Nutzer über Beekeeper Studio sagen
„Beekeeper Studio hat meinen alten SQL-Workflow komplett ersetzt. Es ist schnell, intuitiv und macht die Arbeit mit Datenbanken wieder zum Vergnügen.“
„Ich habe viele Datenbank-GUIs ausprobiert, aber Beekeeper trifft genau die richtige Balance zwischen Funktionsumfang und Einfachheit. Es funktioniert einfach.“