Как найти дубликаты в SQL

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем это знать

Дубликаты ломают аналитику: double counting revenue, завышенные активные пользователи, неверные конверсии. На собесах вопрос «как найти дубликаты в таблице» — базовый screen на SQL.

Чистка дубликатов — одна из частых задач в ETL и ad-hoc анализе. Знать 3-4 способа (и их trade-offs) — обязательно для middle.

Способ 1: GROUP BY + HAVING

Самый читаемый способ — найти строки с COUNT > 1.

SELECT email, COUNT(*) AS cnt
FROM users
GROUP BY email
HAVING COUNT(*) > 1;

Быстро, понятно. Но покажет только duplicate keys — не full rows.

Способ 2: ROW_NUMBER

Нужны все поля дубликатов? Используйте оконную функцию.

WITH ranked AS (
    SELECT *,
           ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at) AS rn
    FROM users
)
SELECT * FROM ranked WHERE rn > 1;

Вернёт все дубликаты кроме первого. Удобно для удаления.

Способ 3: COUNT OVER

Все строки где дубликаты встречаются:

SELECT * FROM (
    SELECT *, COUNT(*) OVER (PARTITION BY email) AS cnt
    FROM users
) t WHERE cnt > 1;

Включая первое вхождение. Для анализа всех duplicate rows.

Удаление дубликатов

Через CTE + DELETE

WITH ranked AS (
    SELECT ctid,
           ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at) AS rn
    FROM users
)
DELETE FROM users
WHERE ctid IN (SELECT ctid FROM ranked WHERE rn > 1);

Postgres-specific (ctid). В MySQL — через primary key.

Через DISTINCT

Если нужно просто получить unique values:

SELECT DISTINCT email, name FROM users;

Не удаляет из таблицы, возвращает unique set.

Дубликаты по composite key

Дубликат по двум полям:

SELECT user_id, product_id, COUNT(*) AS cnt
FROM orders
GROUP BY user_id, product_id
HAVING COUNT(*) > 1;

Часто встречается: одинаковый user + product = duplicate order.

Fuzzy-дубликаты

Одинаковые emails с разным case:

SELECT LOWER(email), COUNT(*) FROM users
GROUP BY LOWER(email) HAVING COUNT(*) > 1;

Или с trailing spaces:

SELECT TRIM(email), COUNT(*) FROM users
GROUP BY TRIM(email) HAVING COUNT(*) > 1;

На собесе

Типичный вопрос: «В таблице users поле email. Найдите всех, у кого email встречается больше одного раза».

Идеальный ответ: покажите GROUP BY, потом упомяните ROW_NUMBER для «если нужны все поля».

Связанные темы

FAQ

Какой способ самый быстрый?

GROUP BY — обычно быстрее ROW_NUMBER на больших данных.

DISTINCT подходит?

Для отдельного выбора unique — да. Для поиска дубликатов — нет.

Как удалить полностью (кроме одной копии)?

ROW_NUMBER + DELETE где rn > 1.


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.