Как убрать дубли событий в SQL и не завысить продуктовую метрику
Практический разбор дедупликации событий в SQL: повторная отправка, idempotency key, ROW_NUMBER и контроль числа пользователей после очистки.
Содержание статьи
После изменения SDK DAU и число действий внезапно выросли. Быстрый взгляд на сырые события показывает много одинаковых строк, но удалять все повторы опасно: для некоторых событий повтор является частью поведения. Как понять, где два одинаковых события означают повтор доставки, а где пользователь действительно совершил действие дважды?
Рабочий вопрос
Как понять, где два одинаковых события означают повтор доставки, а где пользователь действительно совершил действие дважды?
После изменения SDK DAU и число действий внезапно выросли. Быстрый взгляд на сырые события показывает много одинаковых строк, но удалять все повторы опасно: для некоторых событий повтор является частью поведения.
Для event_id с повторной доставкой оставь одну строку через ROW_NUMBER. Для события purchase проверяй order_id, а не только user_id и event_name: два заказа одного человека не являются дублем.
Определение и grain
В сырых данных одна строка может быть попыткой доставки события. В очищенной таблице одна строка должна описывать бизнес-факт, например один просмотр экрана или один заказ.
Собери контрольную выборку, посчитай повторы по event_id и отдельно по составному ключу. После очистки сравни количество событий, уникальных пользователей и сумму денежных событий.
Если показатель строится по событиям, отдельно проверь повторную отправку, идентичность пользователя и часовой пояс. Если данные приходят из бизнес-системы, сопоставь техническое поле с тем, как команда реально принимает решение.
вопрос → объект → период → расчёт → проверка → решениеЛюбой пропущенный шаг может изменить смысл итогового числа.
Разбор примера
Для event_id с повторной доставкой оставь одну строку через ROW_NUMBER. Для события purchase проверяй order_id, а не только user_id и event_name: два заказа одного человека не являются дублем.
Не копируй пример в рабочий отчёт вслепую. Сначала замени названия таблиц и полей, проверь кардинальность связей и запиши, какие строки должны попасть в результат. Хорошая адаптация сохраняет логику, но делает допущения видимыми.
| Событие | Ключ | Риск |
|---|---|---|
| page_view | event_id | повтор доставки |
| purchase | order_id | два заказа одного user_id |
| login | user_id + date | несколько входов в день |
WITH ranked AS (\n SELECT *,\n ROW_NUMBER() OVER (PARTITION BY event_id ORDER BY loaded_at DESC) AS rn\n FROM raw_events\n)\nSELECT * FROM ranked WHERE rn = 1;Где расчёт ломается
Частая ошибка — использовать DISTINCT по всем колонкам и считать задачу решённой. Это не защищает от дублей с разным временем загрузки и может скрыть реальные повторные действия.
Отдельно протестируй пустой результат, NULL, дубль, граничную дату и объект без связанной записи. Эти случаи не являются редкими исключениями: именно они чаще всего превращают рабочий показатель в красивую, но неверную цифру.
- Не смешивай зрелые и незрелые периоды без отдельной подписи.
- Не заменяй проверку качества тем, что запрос просто выполнился.
- Не делай причинный вывод из одного разреза.
Что показывает график
График здесь нужен не для украшения статьи. Он помогает увидеть форму сигнала: тренд, разрыв между сегментами, хвост распределения или этап, на котором теряется объект. Сначала прочитай оси и единицы, затем сравни с таблицей и только после этого формулируй вывод.
На визуализации «События до и после дедупликации» сравнивай не только максимальное значение. Проверь, одинаковы ли базы, не скрыта ли неполная дата и не меняется ли знаменатель от категории к категории.
Условный пример: число строк падает сильнее, чем число уникальных пользователей.
Проверь на своих данных
Создай CTE с ROW_NUMBER, добавь искусственную повторную строку и проверь, что метрика меняется только там, где это ожидается. Затем сравни результат с ручной таблицей из десяти событий.
После упражнения напиши вывод в двух версиях. Первая — техническая: какие строки и условия дали результат. Вторая — для команды: что изменилось, насколько это надёжно и какое действие стоит проверить. Если эти версии противоречат друг другу, вернись к определению показателя.
- Собери контрольный пример из пяти-десяти строк.
- Сверь итог с независимым способом расчёта.
- Проверь хотя бы один крайний случай.
- Зафиксируй дату, версию схемы и ограничение.
Решение и продолжение
Если повторы вызваны доставкой, исправляй ingestion и добавляй idempotency key. Если это реальное поведение, оставляй событие и меняй правило метрики, а не данные задним числом.
Сильный аналитический материал не обещает абсолютной уверенности. Он показывает, как получить число, где оно может ошибиться и какое решение можно принять уже сейчас. Такой формат хорошо переносится в дашборд, SQL-тренажёр, собеседование и рабочее обсуждение с командой.
Материалы по теме
Собеседование аналитика данных: 30 задач и как решать их вслух
Большой практический гайд по собеседованию аналитика данных: SQL, Python, метрики, статистика, кейсы, дашборды и ответы, которые показывают ход мышления.

CAST и типы данных в SQL: почему конверсия равна нулю
Целочисленное деление, numeric против float, текст в число и дату, приведение типов в WHERE и индексы: как готовить данные так, чтобы расчёт не врал молча.

JSONB в PostgreSQL: операторы, типы и запросы к свойствам событий
Как читать JSONB в аналитике: операторы ->, ->>, @> и ?, приведение типов, три вида пропущенного значения, разворачивание массивов и индексы под реальные запросы.