Как посчитать YoY в SQL
Содержание:
Зачем YoY
В ритейле декабрь даёт +50% к ноябрю — это сезон, а не рост бизнеса. Чтобы оценить реальный progress, аналитики используют YoY: декабрь этого года vs декабрь прошлого. Тут уже видно, прибавили мы 5-10% или нет.
YoY — главная метрика для сезонных бизнесов: ритейл, туризм, образование, медиа с разовыми события (премии, выборы). В статье — SQL и нюансы.
Формула YoY
YoY growth (%) = (current_period / same_period_last_year - 1) × 100%
YoY abs = current_period - same_period_last_yearPeriod может быть день, месяц, квартал — главное, чтобы сравнивались одинаковые периоды разных лет.
Базовый расчёт
Данные: 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 '36 months'
GROUP BY 1
)
SELECT
month,
revenue,
LAG(revenue, 12) OVER (ORDER BY month) AS revenue_yoy,
revenue - LAG(revenue, 12) OVER (ORDER BY month) AS yoy_abs,
(revenue::NUMERIC / NULLIF(LAG(revenue, 12) OVER (ORDER BY month), 0) - 1) * 100 AS yoy_pct
FROM monthly
ORDER BY month;LAG(revenue, 12) — значение 12 строк назад в order by month, то есть тот же месяц год назад.
YoY по сегментам
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, 12) OVER (PARTITION BY category ORDER BY month), 0
) - 1) * 100 AS yoy_pct
FROM monthly_cat
WHERE month >= CURRENT_DATE - INTERVAL '13 months'
ORDER BY category, month;Сезонность и YoY
YoY автоматически смывает сезонность. Пример: туркомпания.
- Q3 2026: 100M (высокий сезон)
- Q4 2026: 30M (низкий)
- Q1 2027: 25M
- Q2 2027: 60M
- Q3 2027: 110M (vs Q3 2026 = +10% — реальный рост)
Без YoY сравнение Q3 2027 vs Q2 2027 (+83%) выглядит ложно успешным.
Частые ошибки
Ошибка 1. Сравнивать неполный год. 13 мая 2027 vs 13 мая 2026 — не один к одному. Используйте YTD (year-to-date) vs same period of last year.
Ошибка 2. Integer division.
revenue / prev_year_revenue для INTEGER даёт целое. Используйте ::NUMERIC.
Ошибка 3. NULL при первом году. LAG возвращает NULL для первых 12 месяцев. Это корректно.
Ошибка 4. Праздники в разные даты. Пасха, рамадан, китайский новый год — плавают. Сравнение May 2026 vs May 2027 может включать разное число праздников.
Ошибка 5. Високосный год. Февраль 2024 (29 дней) vs февраль 2026 (28 дней). Различие в одном дне на месячных метриках = ~3,5% шум.
Ошибка 6. YoY на коротких периодах.
YoY на неделях — LAG(value, 52) — но 52 недели ≠ 365 дней (накапливается смещение). Используйте дату-based comparisons.
Связанные темы
FAQ
YoY или MoM?
В сезонных бизнесах — YoY. В нерационально сезонных (SaaS) — MoM.
YoY на день?
Можно: LAG(value, 365). Но из-за високосного года и разного числа выходных — лучше mom level.
YoY показывает рост 200% — реально?
Часто это эффект низкой базы (продукт запустили в феврале 2026). Через год база нормализуется и YoY успокоится.
Какие изменения большие?
В зрелом бизнесе +10-30% YoY — отлично. В стартапе +200-500% — норма (база малая).
YoY vs CAGR?
YoY — годовой прирост между конкретными годами. CAGR — среднегодовой темп за многолетний период.