🔥 Live Coding: пользователи с оплаченной суммой > 100000 за 30 дней
Задача: Есть 2 таблицы:
orders • order_id • user_id • order_time • amount
payments • payment_id • order_id • payment_time • status
Нужно найти пользователей, у которых сумма оплаченных заказов за последние 30 дней больше 100000.
Важно: Заказ считаем оплаченным, если по нему есть хотя бы один payment со status = 'success' ✅
На выходе хотим получить:
• user_id • total_paid_amount
Решение:
with paid_orders as ( select distinct order_id from payments where status = 'success' ),
orders_30d as ( select order_id, user_id, amount from orders where order_time >= now() - interval 30 day )
select o.user_id, sum(o.amount) as total_paid_amount from orders_30d o inner join paid_orders p on o.order_id = p.order_id group by o.user_id having sum(o.amount) > 100000
🧠 Как здесь думаем:
Сначала отдельно определяем оплаченные заказы.
Почему через отдельный CTE? Потому что в payments у одного заказа может быть несколько записей. Например: • одна неуспешная • потом успешная • или даже несколько success
Нам нужно не количество платежей, а сам факт: есть success или нет.
Поэтому в paid_orders берем distinct order_id, где status = 'success'.
Дальше: • отбираем заказы только за последние 30 дней • джойним их с оплаченных заказами • суммируем amount по user_id • оставляем только тех, у кого сумма > 100000
⚠️ Где можно ошибиться:
Сделать обычный join сразу на payments без distinct
Тогда один и тот же заказ может продублироваться несколько раз, если у него несколько успешных платежей. В результате сумма будет завышена ❌
Фильтровать 30 дней по payment_time, а не по order_time
В условии задачи сказано: сумма оплаченных заказов за последние 30 дней
Обычно это читается как заказы, созданные за последние 30 дней. Поэтому фильтр здесь ставим по order_time.
Использовать where вместо having
where работает до агрегации, а нам нужно фильтровать уже посчитанную сумму по пользователю. Поэтому здесь нужен having.
💡 Альтернативный вариант:
Можно решить и через exists. Это тоже хороший и безопасный способ, когда нужно проверить сам факт успешной оплаты.
Например так:
select o.user_id, sum(o.amount) as total_paid_amount from orders o where o.order_time >= now() - interval 30 day and exists ( select 1 from payments p where p.order_id = o.order_id and p.status = 'success' ) group by o.user_id having sum(o.amount) > 100000
Этот вариант часто даже читается проще: для каждого заказа просто проверяем, есть ли успешный платеж.
🎯 Вывод:
Это типовая задача, где важно не просто сделать join, а правильно определить бизнес-смысл оплаченного заказа.
Ключевая мысль: если нужно проверить факт существования события, очень часто лучше думать через: • distinct • exists а не через прямой join на сырые события.
Так запрос получается и корректнее, и безопаснее 👌