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

Проверь себя · 1/3разбор после ответа
У вас есть два набора пользователей одного типа: new_users_ru(user_id) и new_users_kz(user_id). Нужно получить общий список пользователей для дальнейшего соединения с orders. Что сделать на первом шаге?

Зачем аналитику считать MRR в SQL

MRR (Monthly Recurring Revenue) — ключевая метрика любого SaaS-продукта. По ней меряется здоровье бизнеса, она идёт в инвестиционные отчёты и в обсуждения с бордом. Ошибки в расчёте MRR почти всегда приводят к неверным выводам о росте.

Посчитать MRR кажется простым: «сумма активных подписок на дату». На практике возникают ровно те нюансы, на которых и спотыкаются:

  • годовые подписки нужно нормализовать в месячный эквивалент, иначе MRR скачет в месяц оплаты;
  • prorated-подписки (неполный месяц) считаются пропорционально количеству оплаченных дней;
  • триалы в MRR не идут до конвертации;
  • скидки учитываются только в период их действия.

А ещё стандартная декомпозиция — MRR movements: new / expansion / contraction / churn. Без неё непонятно, растёт ли продукт за счёт реальной стоимости или только за счёт верхней воронки.

В статье — готовые SQL-запросы под частые задачи.

Схема данных

subscriptions  (user_id, plan, mrr, billing_period, status, started_at, ended_at)
mrr_events     (user_id, event_type, event_at, mrr_delta)
mrr_snapshots  (user_id, snapshot_date, mrr)

1. MRR на конкретную дату

SELECT
    DATE '2026-04-01'   AS snapshot_date,
    SUM(mrr)             AS total_mrr
FROM subscriptions
WHERE status = 'active'
  AND started_at <= '2026-04-01'
  AND (ended_at IS NULL OR ended_at > '2026-04-01');

Подписка попадает в MRR, если активна на snapshot-дату.

2. MRR по тарифам

SELECT
    plan,
    COUNT(*)      AS active_subs,
    SUM(mrr)      AS total_mrr,
    AVG(mrr)      AS avg_mrr_per_sub
FROM subscriptions
WHERE status = 'active'
GROUP BY plan
ORDER BY total_mrr DESC;

3. Нормализованный MRR для разных тарифных периодов

Если у вас перемешаны monthly / quarterly / annual — нормализуем:

SELECT
    user_id,
    plan,
    CASE
        WHEN billing_period = 'monthly'   THEN amount
        WHEN billing_period = 'quarterly' THEN amount / 3.0
        WHEN billing_period = 'annual'    THEN amount / 12.0
    END AS mrr_normalized
FROM subscriptions
WHERE status = 'active';

Годовая подписка на 12 000 ₽ превращается в MRR 1 000 ₽ — это единственный честный способ показывать динамику MRR, если у части клиентов разные периоды оплаты.

4. MRR по месяцам (time series)

WITH months AS (
    SELECT generate_series(
        '2025-01-01'::DATE,
        DATE_TRUNC('month', CURRENT_DATE)::DATE,
        '1 month'
    )::DATE AS month
),
mrr_snapshots AS (
    SELECT
        m.month,
        SUM(s.mrr) AS mrr
    FROM months m
    LEFT JOIN subscriptions s
        ON s.started_at <= m.month
       AND (s.ended_at IS NULL OR s.ended_at > m.month)
       AND s.status    != 'cancelled'
    GROUP BY m.month
)
SELECT
    month,
    mrr,
    LAG(mrr) OVER (ORDER BY month) AS prev_mrr,
    100.0 * (mrr - LAG(mrr) OVER (ORDER BY month))
          / NULLIF(LAG(mrr) OVER (ORDER BY month), 0) AS mom_growth_pct
FROM mrr_snapshots
ORDER BY month;

С generate_series пустые месяцы не «пропадают» из отчёта — это важно для честного графика.

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

5. MRR movements

Классическая SaaS-разбивка изменения MRR:

SELECT
    DATE_TRUNC('month', event_at)::DATE AS month,
    SUM(CASE WHEN event_type = 'start'      THEN mrr_delta ELSE 0 END) AS new_mrr,
    SUM(CASE WHEN event_type = 'upgrade'    THEN mrr_delta ELSE 0 END) AS expansion,
    SUM(CASE WHEN event_type = 'downgrade'  THEN -mrr_delta ELSE 0 END) AS contraction,
    SUM(CASE WHEN event_type = 'cancel'     THEN -mrr_delta ELSE 0 END) AS churn
FROM mrr_events
GROUP BY 1
ORDER BY 1;

Это и есть ответ на вопрос «за счёт чего вырос MRR»: новые клиенты, расширение существующих, а что потеряли — в downgrade и churn.

6. Net Revenue Retention (NRR)

NRR измеряется на существующей когорте клиентов и показывает, как изменился их MRR за период:

WITH start_mrr AS (
    SELECT user_id, mrr
    FROM mrr_snapshots
    WHERE snapshot_date = '2026-01-01'
),
end_mrr AS (
    SELECT user_id, mrr
    FROM mrr_snapshots
    WHERE snapshot_date = '2026-04-01'
)
SELECT
    SUM(COALESCE(e.mrr, 0)) / NULLIF(SUM(s.mrr), 0) AS nrr
FROM start_mrr s
LEFT JOIN end_mrr e USING (user_id);

Ориентиры:

  • > 100% — расширение существующих клиентов перекрывает отток (редкая здоровая ситуация);
  • 95–100% — типовой диапазон для зрелых B2B SaaS;
  • < 90% — повод разобраться, почему уходят и не расширяются.

7. Когортный MRR retention

WITH cohorts AS (
    SELECT
        user_id,
        DATE_TRUNC('month', first_payment_at)::DATE AS cohort_month
    FROM subscriptions
),
snapshots AS (
    SELECT
        c.cohort_month,
        DATE_TRUNC('month', s.snapshot_date)::DATE AS snapshot_month,
        (EXTRACT(YEAR  FROM AGE(s.snapshot_date, c.cohort_month)) * 12
         + EXTRACT(MONTH FROM AGE(s.snapshot_date, c.cohort_month)))::INT AS months_since_start,
        SUM(s.mrr) AS mrr_in_cohort
    FROM cohorts c
    JOIN mrr_snapshots s USING (user_id)
    GROUP BY 1, 2, 3
)
SELECT
    cohort_month,
    months_since_start,
    mrr_in_cohort
FROM snapshots
ORDER BY cohort_month, months_since_start;

EXTRACT(MONTH FROM AGE(...)) в Postgres возвращает только компонент месяцев (0–11), поэтому собираем целое число через годы × 12 + месяцы — иначе данные за полтора года превратятся в «6 месяцев».

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

1. Путать MRR и cash

MRR измеряет recurring-составляющую дохода. Единоразовые платежи (setup fee, onboarding fee) в MRR не попадают, иначе картина роста будет рваной.

2. Не нормализовать годовые подписки

Годовая подписка в 12 000 ₽ — это MRR 1 000 ₽, а не 12 000 ₽ в месяц оплаты и 0 ₽ в остальные.

3. Триал в MRR

Триал до конверсии в платный план не считается. Иначе MRR будет раздуваться там, где никто ещё не платит.

4. Discounts

Скидка 50% на три месяца уменьшает MRR только на эти три месяца. Потом MRR возвращается к базовой стоимости (если не уехал в churn).

5. Путать NRR и Gross Revenue Retention

NRR учитывает expansion, Gross Revenue Retention — только потери (contraction + churn). Показывайте в отчёте обе метрики, они отвечают на разные вопросы.

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

FAQ

MRR или ARR?

MRR удобнее для оперативного мониторинга, ARR (MRR × 12) — для board-репортов и годовых презентаций.

Как учитывать возвраты?

Refund вычитается из MRR того месяца, в котором он случился. Если refund на весь период — в месяце возврата подписка должна считаться закрытой.

Usage-based pricing — как считать MRR?

Для тарифов с плавающим потреблением выделяют committed MRR (минимальный гарантированный платёж) и expansion MRR (overage за фактическое потребление).

Как сверить MRR с бухгалтерией?

Удобно держать отчёт, в котором в одном разрезе показаны MRR, billed amount и collected cash — расхождения сразу бросаются в глаза.