Раздел 42 · Аналитика данных с нуля

Дубликаты и пропуски: найти, понять, решить

junior~15 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Бран · продюсер «Хроник Пограничья»

Финансисты принесли сверку: по нашим отчётам магазин заработал больше, чем пришло на счёт. Разница в десятки тысяч. Кто-то ворует или мы что-то считаем дважды?

В прошлом уроке ты чистил строки: пробелы, регистр, тестовые почты. Сегодня грязь другого сорта. Строка сама по себе аккуратная, но её не должно быть вовсе: это копия другой строки. Или наоборот, строка на месте, а значения в ней нет. Учимся находить дубли и пропуски, понимать, откуда они взялись, и решать, что с ними делать.

Курьер звонит дважды

Представь курьера, который принёс посылку, не дождался подписи и через минуту позвонил в дверь снова с той же посылкой. Если бухгалтер считает звонки, посылок у него две. Если смотрит на номер накладной, одна.

С покупками в игре происходит то же. Когда игрок платит, магазин приложений сообщает об этом серверу игры сигналом, его называют вебхук. Если сервер ответил с опозданием, магазин на всякий случай повторяет сигнал, и в purchases появляется вторая строка про ту же покупку. Номер накладной у покупки есть: это receipt_id, номер чека. Он одинаковый у обеих копий.

Найти дубли

Дубликат ищут группировкой по тому, что должно быть уникальным. Каждый чек должен встречаться в таблице один раз, значит группируем по receipt_id и оставляем группы, где строк больше одной:

SELECT receipt_id, count(*) AS copies
FROM purchases
GROUP BY receipt_id
HAVING count(*) > 1;
    receipt_id    | copies
------------------+--------
 and-0c13f52a6e63 |      2
 and-1b07143fc37e |      2
 and-3a11455c62b3 |      2
 ...

Таких чеков 28. Посмотри на один из них целиком:

SELECT id, player_id, offer_id, amount_rub, status, created_at
FROM purchases
WHERE receipt_id = 'and-0c13f52a6e63';
 id  | player_id | offer_id | amount_rub | status |       created_at
-----+-----------+----------+------------+--------+------------------------
 532 |       243 |        2 |     499.00 | paid   | 2026-03-27 17:22:21+00
 533 |       243 |        2 |     499.00 | paid   | 2026-03-27 17:22:26+00

Игрок тот же, оффер тот же, сумма та же, разница в пять секунд. Это не второй «Мешок гемов», это эхо первого. Самое долгое эхо в базе пришло через 37 секунд.

Сколько всего лишних строк, можно посчитать одной строкой: все строки минус уникальные чеки.

SELECT count(*) - count(DISTINCT receipt_id) AS extra_copies
FROM purchases;

Ответ 28. Здесь совпало с числом чеков-дублей, потому что у каждого ровно одна лишняя копия. Если бы магазин повторил сигнал дважды, чек был бы один, а лишних строк две. Это ловушка третьего квеста.

урок из курса

Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.