Как посчитать WoW в SQL

Закрепи формулу wow в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать wow в Telegram

Зачем WoW

Менеджер: «У нас вчера упала выручка на 15% — что делать?». Аналитик смотрит WoW: вчерашняя пятница vs прошлая пятница — рост +8%. День просто «слабый» в принципе. Падение было нормальным dip от среды к пятнице, а WoW убирает сезонность дня недели.

WoW (week-over-week) — рабочая метрика для продуктов с недельной сезонностью: e-com, доставка, контент. Помогает увидеть тренд без шума weekend/weekday. В статье — SQL через LAG и нюансы (рваные недели, праздники).

Что такое WoW

WoW (Week-over-Week) — изменение метрики vs аналогичный период неделей раньше.

WoW growth (%) = (current_week_value / previous_week_value - 1) × 100%
WoW abs       = current_week_value - previous_week_value

Можно считать в двух режимах:

  • WoW по неделям: сравнение totals недель (W vs W-1)
  • WoW по дням: сравнение каждого дня с тем же днём недели неделей раньше (Mon vs Mon, Tue vs Tue)

Второй вариант учитывает сезонность дня недели и обычно полезнее.

Базовый расчёт

WoW по неделям

Данные: daily_revenue(day, revenue).

WITH weekly AS (
    SELECT
        DATE_TRUNC('week', day) AS week,
        SUM(revenue) AS revenue
    FROM daily_revenue
    WHERE day >= CURRENT_DATE - INTERVAL '12 weeks'
    GROUP BY 1
)
SELECT
    week,
    revenue,
    LAG(revenue) OVER (ORDER BY week) AS prev_week_revenue,
    revenue - LAG(revenue) OVER (ORDER BY week) AS wow_abs,
    (revenue::NUMERIC / NULLIF(LAG(revenue) OVER (ORDER BY week), 0) - 1) * 100 AS wow_pct
FROM weekly
ORDER BY week;

Важно: DATE_TRUNC('week', ...) начинает неделю с понедельника в Postgres. Если нужна неделя с воскресенья — рассчитайте смещение.

WoW по дням

SELECT
    day,
    revenue,
    LAG(revenue, 7) OVER (ORDER BY day) AS same_day_prev_week,
    revenue - LAG(revenue, 7) OVER (ORDER BY day) AS wow_abs,
    (revenue::NUMERIC / NULLIF(LAG(revenue, 7) OVER (ORDER BY day), 0) - 1) * 100 AS wow_pct
FROM daily_revenue
WHERE day >= CURRENT_DATE - INTERVAL '90 days'
ORDER BY day;

LAG(revenue, 7) — значение 7 строк назад в order by day. Сравнивает Mon vs prev Mon без агрегации.

WoW по сегментам

«Какие категории растут быстрее»:

WITH weekly_cat AS (
    SELECT
        DATE_TRUNC('week', created_at) AS week,
        category,
        SUM(total) AS revenue
    FROM orders
    WHERE status = 'paid'
      AND created_at >= CURRENT_DATE - INTERVAL '4 weeks'
    GROUP BY 1, 2
)
SELECT
    week,
    category,
    revenue,
    (revenue::NUMERIC / NULLIF(
        LAG(revenue) OVER (PARTITION BY category ORDER BY week), 0
    ) - 1) * 100 AS wow_pct
FROM weekly_cat
ORDER BY category, week;

PARTITION BY category — LAG считается отдельно для каждой категории.

Закрепи формулу wow в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать wow в Telegram

WoW vs MoM vs YoY

Метрика Сравнение Когда использовать
WoW Текущая неделя vs предыдущая Высокочастотные продукты, weekly tracking
MoM Текущий месяц vs предыдущий Месячные циклы, payments, subscriptions
YoY Текущий период vs тот же годом раньше Сезонные бизнесы (праздники, школа, лето)

YoY автоматически смывает сезонность — Q4 этого года vs Q4 прошлого года. В сезонных бизнесах (туризм, ритейл) MoM или WoW могут вводить в заблуждение.

SELECT
    DATE_TRUNC('month', created_at) AS month,
    SUM(total) AS revenue,
    LAG(SUM(total), 1)  OVER (ORDER BY DATE_TRUNC('month', created_at)) AS prev_month,
    LAG(SUM(total), 12) OVER (ORDER BY DATE_TRUNC('month', created_at)) AS prev_year,
    (SUM(total)::NUMERIC
     / NULLIF(LAG(SUM(total), 1)  OVER (ORDER BY DATE_TRUNC('month', created_at)), 0) - 1) * 100 AS mom_pct,
    (SUM(total)::NUMERIC
     / NULLIF(LAG(SUM(total), 12) OVER (ORDER BY DATE_TRUNC('month', created_at)), 0) - 1) * 100 AS yoy_pct
FROM orders
WHERE status = 'paid'
GROUP BY 1
ORDER BY 1;

WoW с сглаживанием

WoW бывает шумным. Сглаживание через 4-week rolling смягчает разовые провалы:

WITH weekly AS (
    SELECT
        DATE_TRUNC('week', created_at) AS week,
        SUM(total) AS revenue
    FROM orders
    WHERE status = 'paid'
    GROUP BY 1
)
SELECT
    week,
    revenue,
    AVG(revenue) OVER (
        ORDER BY week
        ROWS BETWEEN 3 PRECEDING AND CURRENT ROW
    ) AS revenue_4w_avg,
    (AVG(revenue) OVER (ORDER BY week ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
     / NULLIF(AVG(revenue) OVER (ORDER BY week ROWS BETWEEN 7 PRECEDING AND 4 PRECEDING), 0) - 1) * 100 AS smoothed_4w_growth
FROM weekly
ORDER BY week;

Сравнение 4-week avg с предыдущими 4-week avg — устойчивее, чем raw WoW.

Частые ошибки

Ошибка 1. Сравнивать неполную неделю

Сегодня среда. «WoW: эта неделя vs прошлая» — но эта неделя ещё не закончилась. Сравнивайте только полные недели или Mon-сегодня vs Mon-(сегодня-7).

Ошибка 2. Праздники как обычные дни

23 февраля или 8 марта изменили поведение. WoW резко прыгает. Решение: либо отметить holiday в данных, либо использовать YoY вместо WoW.

Ошибка 3. Integer division

revenue / prev_revenue для integer — округление до целого. Используйте ::NUMERIC или 1.0 *.

Ошибка 4. NULL при первой неделе

Первая запись в LAG возвращает NULL, и WoW=NULL — это корректно. Не filler-те нулями, отображайте как «нет данных».

Ошибка 5. DATE_TRUNC сдвигается по часовому поясу

Если данные хранятся в UTC, а компания в МСК — неделя начинается в воскресенье 21:00 UTC. Сначала приведите к нужной timezone: DATE_TRUNC('week', created_at AT TIME ZONE 'Europe/Moscow').

Ошибка 6. WoW от очень маленьких чисел

База 5 → 15 = +200%. Звучит впечатляюще, но абсолютно — это +10. Всегда показывайте и pct, и абсолютные значения.

Связанные темы

FAQ

WoW или WoW% — что показывать?

Обе. Абсолютные — про реальные числа. Процентные — про темп. Только % обманчиво при маленькой базе (5 → 15 = +200%).

Как считать WoW для метрик с порогом нуля?

Если в одной неделе 0 — WoW не определён. Используйте absolute change или WoW with baseline (например, среднее за 4 недели).

WoW vs 7-day rolling — в чём разница?

WoW — точечное сравнение. 7-day rolling avg — сглаживание шума. Часто используют вместе: WoW на rolling avg вместо raw value.

Можно ли WoW на даже более коротких периодах (HoH)?

«Hour-over-hour» или «Day-over-day» — да, но шум огромный. Для трендов лучше 7DMA или WoW.

Как объяснить менеджменту, почему WoW колеблется?

Декомпозиция факторов: трафик, конверсия, AOV, отдельные сегменты. Один shock в одной размерности маскирует тренды в других.