Как сравнить два периода в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем это знать
«Выручка выросла» — вырасла по сравнению с чем? Без сравнения с прошлым периодом цифра бессмысленна. WoW (week-over-week), MoM (month-over-month), YoY (year-over-year) — базовые операции в любом BI-отчёте.
На собесах часто: «сравните MoM revenue» или «найди, где падение WoW > 10%». Типичный middle-вопрос.
Способ 1: LAG
SELECT
month,
revenue,
LAG(revenue) OVER (ORDER BY month) AS prev_month,
revenue - LAG(revenue) OVER (ORDER BY month) AS mom_abs,
(revenue - LAG(revenue) OVER (ORDER BY month)) * 100.0 /
LAG(revenue) OVER (ORDER BY month) AS mom_pct
FROM monthly_revenue;LAG даёт предыдущую строку. Простое MoM.
Способ 2: Self-join
SELECT
t.month, t.revenue, p.revenue AS prev, t.revenue - p.revenue AS diff
FROM monthly_revenue t
LEFT JOIN monthly_revenue p ON p.month = t.month - INTERVAL '1 month';Менее элегантно, но явно видно сравнение.
Способ 3: conditional aggregation
SELECT
SUM(CASE WHEN month = '2026-03-01' THEN revenue END) AS mar,
SUM(CASE WHEN month = '2026-04-01' THEN revenue END) AS apr
FROM monthly_revenue;Для конкретных периодов — удобно.
YoY
LAG с 12 months:
SELECT month, revenue,
LAG(revenue, 12) OVER (ORDER BY month) AS yoy_prev
FROM monthly_revenue;Второй аргумент LAG — шаг назад.
WoW
Если data weekly:
SELECT week, metric, LAG(metric) OVER (ORDER BY week) AS prev
FROM weekly_stats;Или daily → сначала aggregate по week:
WITH weekly AS (
SELECT DATE_TRUNC('week', day) AS week, SUM(revenue) AS rev
FROM daily_revenue GROUP BY 1
)
SELECT week, rev, LAG(rev) OVER (ORDER BY week) AS prev_week
FROM weekly;% change
(CURRENT - previous) * 100.0 / NULLIF(previous, 0)NULLIF(previous, 0) защищает от деления на 0.
Сравнение по группам
MoM по продуктам:
SELECT
product,
month,
revenue,
LAG(revenue) OVER (PARTITION BY product ORDER BY month) AS prev
FROM product_revenue;PARTITION BY — отдельно для каждого продукта.
Период vs Период (две произвольные даты)
«March 1-15 vs April 1-15»:
WITH periods AS (
SELECT
SUM(CASE WHEN DATE(created_at) BETWEEN '2026-03-01' AND '2026-03-15' THEN total END) AS mar,
SUM(CASE WHEN DATE(created_at) BETWEEN '2026-04-01' AND '2026-04-15' THEN total END) AS apr
FROM orders
)
SELECT mar, apr, apr - mar AS diff, (apr - mar) * 100.0 / mar AS pct_change
FROM periods;Seasonality considerations
Сравнение April vs March может врать из-за seasonality. Честное сравнение:
- YoY (year-over-year) убирает seasonal effects
- Сравнение с ожидаемым baseline, не с прошлым месяцем
Частые ошибки
Деление на 0
Если previous = 0 → division error. Используйте NULLIF.
Не учли partitioning
LAG без PARTITION BY смешает продукты/каналы/cohorts.
Неверные границы
BETWEEN '2026-04-01' AND '2026-04-30' — включит весь день 30-го? Да, но timestamp с временем — нет. Используйте >= '2026-04-01' AND < '2026-05-01'.
На собесе
«Найди месяцы, где MoM revenue упал больше чем на 20%».
WITH mom AS (
SELECT month, revenue,
LAG(revenue) OVER (ORDER BY month) AS prev,
(revenue - LAG(revenue) OVER (ORDER BY month)) * 100.0 /
LAG(revenue) OVER (ORDER BY month) AS pct
FROM monthly_revenue
)
SELECT * FROM mom WHERE pct < -20;Связанные темы
FAQ
LAG или self-join?
LAG — чище и быстрее на одной таблице. Self-join — если нужно сравнивать с другой.
Как считать % для отрицательного baseline?
Формула та же, но интерпретировать осторожно. Лучше absolute diff.
YoY всегда лучше MoM?
Нет. YoY убирает seasonality, MoM — показывает momentum.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.