Короткий ответ
Дубликаты обычно ищут через GROUP BY по ключевым полям с HAVING COUNT(*) > 1, либо через оконную функцию ROW_NUMBER() для более гибкого определения «что считать дублем».
Как это работает подробнее
GROUP BY + HAVING COUNT(*) > 1 группирует строки по выбранным колонкам и оставляет только те группы, где больше одной строки — это и есть дубликаты по этим колонкам. ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...) присваивает номер строкам внутри группы, что удобно для выбора «оставить только первую запись».
Пример кода
Пример на SQL
-- PostgreSQL: поиск email, зарегистрированных более одного раза
SELECT email, COUNT(*) AS cnt
FROM users
GROUP BY email
HAVING COUNT(*) > 1;
-- Альтернатива с ROW_NUMBER для последующего удаления лишних
SELECT *, ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at) AS rn
FROM users;Что использовать на практике
- На практике: перед поиском дублей нужно явно решить, что считается дублем: полное совпадение всех полей, или совпадение по бизнес-ключу (например, email) при разных ID. Часто дубли возникают из-за повторной отправки формы, сбоя интеграции или отсутствия уникального ограничения в таблице — стоит не только почистить данные, но и найти источник проблемы. Полезно также проверять, не возникают ли дубли систематически в определённый период (например, после сбоя интеграции), что указывает на техническую, а не случайную природу проблемы.
- Проверка: для email с cnt=2 открыть обе записи и сравнить остальные поля — убедиться, что это действительно ошибка, а не два разных человека с одинаковым служебным email.
Подводные камни
- Частые ошибки: удалять дубликаты через
DISTINCTне разобравшись, какие строки реально идентичны, а какие лишь похожи; искать дубли только по ID, который как раз уникален по построению, и не найти настоящую проблему; не учитывать, что часть «дублей» может быть легитимными повторными заказами одного клиента. Ещё одна ошибка — путать legitimate повторные записи (две покупки одного клиента в разные дни) с настоящими дублями одного и того же события.