Как посчитать MoM в SQL
Содержание:
Зачем MoM
Менеджер: «Выручка в апреле упала на 5% — что делать?». Аналитик смотрит MoM по году: каждый апрель -3-7% относительно марта — это сезонная нормальность (праздники, корпоративные траты завершаются). Без MoM-контекста реакция была бы паникой.
MoM — рабочая метрика для месячных циклов: SaaS-подписки, биллинг, маркетинговые кампании. В статье — SQL через LAG и нюансы.
Формула MoM
MoM growth (%) = (current_month / previous_month - 1) × 100%
MoM abs = current_month - previous_monthВ отличие от WoW (понедельная), MoM сглаживает дневной шум, но не учитывает сезонность месяца.
Базовый расчёт
Данные: daily_revenue(day, revenue).
WITH monthly AS (
SELECT
DATE_TRUNC('month', day) AS month,
SUM(revenue) AS revenue
FROM daily_revenue
WHERE day >= CURRENT_DATE - INTERVAL '24 months'
GROUP BY 1
)
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_month,
revenue - LAG(revenue) OVER (ORDER BY month) AS mom_abs,
(revenue::NUMERIC / NULLIF(LAG(revenue) OVER (ORDER BY month), 0) - 1) * 100 AS mom_pct
FROM monthly
ORDER BY month;Важно: DATE_TRUNC('month', ...) группирует по календарному месяцу. Если месяц неполный — MoM может быть искажён.
MoM по сегментам
WITH monthly_cat AS (
SELECT
DATE_TRUNC('month', created_at) AS month,
category,
SUM(total) AS revenue
FROM orders
WHERE status = 'paid'
GROUP BY 1, 2
)
SELECT
month,
category,
revenue,
(revenue::NUMERIC / NULLIF(
LAG(revenue) OVER (PARTITION BY category ORDER BY month), 0
) - 1) * 100 AS mom_pct
FROM monthly_cat
ORDER BY category, month;MoM vs WoW vs YoY
| Метрика | Сравнение | Когда |
|---|---|---|
| WoW | неделя vs предыдущая | Высокочастотные, weekly tracking |
| MoM | месяц vs предыдущий | SaaS, биллинг, подписки |
| YoY | период vs тот же год назад | Сезонные бизнесы |
В сезонных категориях MoM может вводить в заблуждение — Q4 vs Q3 всегда «растёт» в ритейле, но это не успех команды.
Сглаживание MoM
3-month rolling avg смягчает разовые провалы:
WITH monthly AS (
SELECT
DATE_TRUNC('month', created_at) AS month,
SUM(total) AS revenue
FROM orders
WHERE status = 'paid'
GROUP BY 1
)
SELECT
month,
revenue,
AVG(revenue) OVER (
ORDER BY month
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS revenue_3m_avg
FROM monthly
ORDER BY month;Частые ошибки
Ошибка 1. Сравнивать неполный месяц. Сегодня 13 мая. «MoM = май vs апрель» — но май ещё не закончился. Лучше month-to-date vs same period prior month.
Ошибка 2. Integer division.
В Postgres revenue / prev_revenue для INTEGER даёт целое. Используйте ::NUMERIC или 1.0 *.
Ошибка 3. Сезонность. В ритейле декабрь всегда +30% к ноябрю. MoM растёт, но это сезон. YoY лучше для сезонных.
Ошибка 4. NULL при первом месяце. LAG возвращает NULL для первой строки. Это корректно — не заполняйте нулями.
Ошибка 5. Timezone.
DATE_TRUNC в UTC может разбить месяц иначе, чем в МСК. Приводите к нужной timezone.
Связанные темы
FAQ
MoM или WoW?
В SaaS / подписке — MoM. В высокочастотном продукте (e-com, mobility) — WoW. В сезонных — YoY.
Что делать с неполным месяцем?
Month-to-date vs same period prior month. Или waiting until полный.
MoM может быть отрицательным?
Да. -5% — норма для сезонного спада, -20% — серьёзный сигнал.
Какие изменения большие?
В growth-стартапе +5-15% MoM — норма. В зрелом бизнесе ±2-3% — обычные колебания.
MoM на пользовательских метриках?
DAU не имеет смысла мерить MoM — это daily. MAU — да, MoM правильно.