Как найти дубликаты в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Три основных подхода
- GROUP BY + HAVING — простейший способ
- ROW_NUMBER + подзапрос — удобно для вывода полных строк
- SELF JOIN — через соединение
1. Найти дубликаты по одной колонке
Таблица users(id, email, name).
Найти дублирующиеся email:
SELECT email, COUNT(*) AS cnt
FROM users
GROUP BY email
HAVING COUNT(*) > 1;Результат:
email | cnt
----------------------|----
alice@example.com | 2
bob@example.com | 32. Найти дубликаты по нескольким колонкам
Дубли по (first_name, last_name, birth_date):
SELECT first_name, last_name, birth_date, COUNT(*) AS cnt
FROM users
GROUP BY first_name, last_name, birth_date
HAVING COUNT(*) > 1;3. Вывести полные строки дубликатов
Нужны все поля, не только агрегаты:
Через ROW_NUMBER (самый чистый способ)
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn,
COUNT(*) OVER (PARTITION BY email) AS cnt
FROM users
) t
WHERE cnt > 1;Возвращает все строки, где email встречается >1 раза.
Через подзапрос + IN
SELECT * FROM users
WHERE email IN (
SELECT email FROM users
GROUP BY email
HAVING COUNT(*) > 1
);Через EXISTS
SELECT u.* FROM users u
WHERE EXISTS (
SELECT 1 FROM users u2
WHERE u2.email = u.email AND u2.id != u.id
);4. Полные дубликаты строки (все поля одинаковые)
Найти строки, которые дублируются полностью:
SELECT *, COUNT(*) OVER (PARTITION BY col1, col2, col3, ...) AS cnt
FROM my_table
WHERE cnt > 1;Или через CTE и список всех колонок:
WITH dupes AS (
SELECT col1, col2, col3, COUNT(*) AS cnt
FROM my_table
GROUP BY col1, col2, col3
HAVING COUNT(*) > 1
)
SELECT t.*
FROM my_table t
JOIN dupes d ON (t.col1 = d.col1 AND t.col2 = d.col2 AND t.col3 = d.col3);5. Только вторые и последующие вхождения (первый не считать)
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY id) AS rn
FROM users
) t
WHERE rn > 1;rn = 1 — первое вхождение. rn > 1 — последующие (дубликаты).
6. Примеры из практики
Найти дубли заказов по (user_id, product_id, created_date)
Возможно, при сбое запроса создались два заказа:
SELECT user_id, product_id, DATE(created_at) AS d, COUNT(*) AS cnt
FROM orders
GROUP BY user_id, product_id, DATE(created_at)
HAVING COUNT(*) > 1;Найти дубли event-ов от трекера
Двойные события (клик 2 раза подряд за <1 сек):
WITH numbered AS (
SELECT
*,
LAG(event_at) OVER (PARTITION BY user_id, event_type ORDER BY event_at) AS prev_at
FROM events
)
SELECT *
FROM numbered
WHERE event_at - prev_at < INTERVAL '1 second';Найти пользователей, зарегистрировавшихся с одного email
SELECT email, COUNT(DISTINCT user_id) AS uniq_users
FROM users
GROUP BY email
HAVING COUNT(DISTINCT user_id) > 1;7. Дубликаты с учётом case-insensitive
Email Alice@example.com ≠ alice@example.com в строгом сравнении. Но логически — один:
SELECT LOWER(email), COUNT(*) AS cnt
FROM users
GROUP BY LOWER(email)
HAVING COUNT(*) > 1;8. Дубликаты с учётом trim / whitespace
Иногда дубликаты скрыты пробелами: 'Alice' и 'Alice ':
SELECT TRIM(name), COUNT(*) AS cnt
FROM users
GROUP BY TRIM(name)
HAVING COUNT(*) > 1;9. Посчитать сколько дубликатов всего
SELECT
COUNT(*) AS total_rows,
COUNT(DISTINCT email) AS unique_emails,
COUNT(*) - COUNT(DISTINCT email) AS extra_duplicates
FROM users;10. Найти «самые частые» дубликаты
SELECT email, COUNT(*) AS cnt
FROM users
GROUP BY email
HAVING COUNT(*) > 1
ORDER BY cnt DESC
LIMIT 10;Частые ошибки
Ошибка 1. Забыть HAVING
-- вернёт ВСЕ группы, не только дубли
SELECT email, COUNT(*) FROM users GROUP BY email;
-- правильно
SELECT email, COUNT(*) FROM users GROUP BY email HAVING COUNT(*) > 1;Ошибка 2. Не учитывать NULL
Если email IS NULL, GROUP BY группирует NULL вместе:
-- 10 пользователей с NULL email → попадут в «дубликаты»
GROUP BY email HAVING COUNT(*) > 1Фильтруйте:
SELECT email, COUNT(*)
FROM users
WHERE email IS NOT NULL
GROUP BY email
HAVING COUNT(*) > 1;Ошибка 3. Дубликаты по case / пробелам
Данные часто неопрятные. Нормализуйте перед сравнением: LOWER(TRIM(col)).
Ошибка 4. SELECT DISTINCT вместо GROUP BY
-- не скажет, сколько дублей
SELECT DISTINCT email FROM users;
-- даст и email, и количество
SELECT email, COUNT(*) FROM users GROUP BY email;Связанные темы
- Как удалить дубликаты в SQL
- GROUP BY — шпаргалка
- ROW_NUMBER vs RANK vs DENSE_RANK
- DISTINCT vs GROUP BY
FAQ
Как быстрее найти дубликаты?
На индексированной колонке — любой из способов быстрый. Без индекса — GROUP BY более-менее стабильный.
GROUP BY или ROW_NUMBER?
GROUP BY + HAVING — чтобы увидеть, сколько дублей. ROW_NUMBER — чтобы получить полные строки дубликатов.
Как найти дубликаты в нескольких таблицах?
UNION ALL двух таблиц, потом GROUP BY.
Что делать после того, как нашли дубликаты?
Разобраться с причиной (bug в приложении, ETL-дубль). Потом удалить → см. как удалить дубликаты.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.