🧚 Hör mal! Beekeeper Studio ist eine schnelle, moderne Open-Source-Datenbank-GUI Download
9. Januar 2026 Von Matthew Rathbone

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 BY stehen oder in einer Aggregatfunktion. Wenn du name zur 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.dev und ana@silva.dev es 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:

Offizielle Dokumentation:

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.

Die Linux-Version von Beekeeper hat 100% des Funktionsumfangs, ohne Abstriche und ohne Kompromisse.

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.“
— Alex K., Datenbankentwickler
★★★★★
„Ich habe viele Datenbank-GUIs ausprobiert, aber Beekeeper trifft genau die richtige Balance zwischen Funktionsumfang und Einfachheit. Es funktioniert einfach.“
— Sarah M., Full Stack Engineer

Bereit für einen besseren SQL-Workflow?

download Kostenlos herunterladen