Условие
Найти всех клиентов, у которых торговый оборот в текущем месяце вырос более чем на 10% относительно предыдущего месяца, и вывести коммуникации, которые они получили за прошлые 30 дней.
Таблицы — те же, что в задаче по партнёрской рассылке (purchases(ow_id, partner_id, pos, cash_dt), comms(ow_id, sent_date, channel_nm, partner_id, ...)).
Решение
Подход
- Считаем оборот по клиенту по месяцам.
- Текущий и предыдущий месяц через
LAG(или self-join). - Фильтр
current / prev > 1.1. - Подтягиваем коммуникации за прошлые 30 дней.
Реализация
WITH monthly AS (
SELECT
ow_id,
DATE_TRUNC('month', cash_dt) AS m,
SUM(pos) AS total_pos
FROM purchases
GROUP BY ow_id, DATE_TRUNC('month', cash_dt)
),
with_prev AS (
SELECT
ow_id, m, total_pos,
LAG(total_pos) OVER (PARTITION BY ow_id ORDER BY m) AS prev_pos
FROM monthly
),
growers AS (
SELECT ow_id
FROM with_prev
WHERE m = DATE_TRUNC('month', CURRENT_DATE)
AND prev_pos > 0
AND total_pos > prev_pos * 1.10
)
SELECT
g.ow_id,
c.sent_date,
c.channel_nm,
c.partner_id,
c.sent, c.delivered, c.opened, c.clicked
FROM growers g
JOIN comms c ON c.ow_id = g.ow_id
WHERE c.sent_date >= CURRENT_DATE - INTERVAL '30 day'
ORDER BY g.ow_id, c.sent_date;Граничные случаи
prev_pos IS NULL— клиент первый раз появился в текущем месяце. Формально «рост на бесконечность». Решение бизнеса: либо включаем (prev_pos IS NULL OR ...), либо исключаем как «новые без истории». В коде явноprev_pos > 0исключает их.prev_pos = 0не возникнет, если вmonthlyнет нулевых строк (нет агрегации по пустым месяцам). Если делать черезcross joinсо справочником месяцев — учтите деление на 0.- Текущий месяц неполный — рост 10% к полному предыдущему месяцу некорректен. Логичнее сравнивать первые N дней с первыми N днями.
Подводные камни
- Сравнение неполного и полного месяца — частая ошибка. Либо ждём конца месяца, либо нормализуем по дням / используем «скользящие 30 дней vs предыдущие 30».
- Rounding в условии 10%.
> prev * 1.1или>= prev * 1.1? Явно проговорить, чтобы не было «потерянных» клиентов на границе. - Дубли коммуникаций. Один пуш может отправиться несколько раз — если в задаче «вывести коммуникации», обычно нужно
DISTINCTили агрегировать. - Партиции в
LAG. БезPARTITION BY ow_idсместится с предыдущим клиентом — катастрофа. - Месяцы с дырами. Если у клиента не было покупок в марте,
LAGдаст февраль. В большинстве кейсов это ОК (мы сравниваем с «прошлой непустой активностью»), но если требуется именно «прошлый календарный месяц», нужно другое решение черезcross join.
Альтернатива через self-join
SELECT cur.ow_id
FROM monthly cur
JOIN monthly prev
ON cur.ow_id = prev.ow_id
AND prev.m = cur.m - INTERVAL '1 month'
WHERE cur.m = DATE_TRUNC('month', CURRENT_DATE)
AND cur.total_pos > prev.total_pos * 1.10;Это вариант со строгой проверкой именно прошлого календарного месяца.
Эталонный ответ
LAG на месячных агрегатах оборота → фильтр current > prev * 1.1 → JOIN с comms за прошлые 30 дней. Внимательно с prev IS NULL и неполным текущим месяцем.