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

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

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

MRR (Monthly Recurring Revenue) — главная метрика любого SaaS-бизнеса. По ней меряется здоровье продукта, её смотрят инвесторы, по ней распределяют бюджеты. Growth rate = (MRR_new − MRR_old) / MRR_old — именно это число стоит в питчдеках.

Посчитать MRR кажется простым: «сумма активных подписок за месяц». На практике всплывают десятки нюансов. Годовые подписки нужно нормализовать в месячный эквивалент (или MRR будет скакать в месяц оплаты). Prorated подписки (неполный месяц) считаются пропорционально. Trial не идёт в MRR до конвертации. Discounts уменьшают MRR, но только в период действия.

А ещё MRR movements: new, expansion, contraction, churn. Без этой декомпозиции вы не знаете, растёт ли продукт или просто набирает новых клиентов через маркетинг. На собеседовании для SaaS-компаний это одна из первых задач.

В статье — готовые SQL-запросы:

  • Базовый MRR на конкретную дату
  • MRR по тарифам и сегментам
  • Normalized MRR из годовых подписок
  • MRR movements (New / Expansion / Contraction / Churn)
  • Net Revenue Retention (NRR) — ключевая SaaS-метрика
  • Cohort MRR retention

Схема: subscriptions(user_id, plan, mrr, status, started_at, ended_at).

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. Normalized MRR из годовых подписок

Если плата раз в год — нельзя её целиком отнести на один месяц. Нормализуем:

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

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

WITH months AS (
    SELECT generate_series('2025-01-01'::DATE, '2026-04-01'::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,
    (mrr - LAG(mrr) OVER (ORDER BY month)) /
        NULLIF(LAG(mrr) OVER (ORDER BY month), 0) * 100 AS mom_growth_pct
FROM mrr_snapshots
ORDER BY month;

5. MRR movements

Разложение изменения MRR на компоненты — must-have для SaaS-отчёта.

WITH events AS (
    SELECT
        DATE_TRUNC('month', event_at) AS month,
        CASE
            WHEN event_type = 'start' THEN 'new'
            WHEN event_type = 'upgrade' THEN 'expansion'
            WHEN event_type = 'downgrade' THEN 'contraction'
            WHEN event_type = 'cancel' THEN 'churn'
        END AS movement_type,
        mrr_delta
    FROM mrr_events
    WHERE DATE_TRUNC('month', event_at) = '2026-04-01'
)
SELECT
    movement_type,
    SUM(mrr_delta) AS mrr_change,
    COUNT(*) AS movements_count
FROM events
GROUP BY movement_type;

6. Net Revenue Retention (NRR)

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

NRR > 100% — expansion перекрывает churn (идеал). NRR 95-100% — норма для B2B SaaS. NRR < 90% — тревожно.

7. Cohort MRR retention

WITH cohorts AS (
    SELECT user_id, DATE_TRUNC('month', first_payment_at) AS cohort_month
    FROM subscriptions
),
snapshots AS (
    SELECT
        c.cohort_month,
        DATE_TRUNC('month', s.snapshot_date) AS snapshot_month,
        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 * FROM snapshots
ORDER BY cohort_month, months_since_start;

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

Путать MRR и cash

MRR — recurring. Единоразовые платежи (setup fee) в MRR не идут.

Не нормализовать annual

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

Считать MRR для trial

Trial в MRR не входит. Когда конвертируется в paid — тогда.

Discounts на весь период

Discount 50% на 3 месяца снижает MRR только на эти 3 месяца, потом возвращается.

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

FAQ

MRR или ARR?

MRR для operational. ARR = MRR × 12 для отчётов инвесторам.

Как учесть refunds?

Вычитать из MRR месяца, когда случился refund.

Trial + discounts — как?

Trial не в MRR. Discount — фактическая MRR после скидки.

MRR для usage-based pricing?

Сложно — доход нестабильный. Используют «committed MRR» (минимальный) + «expansion MRR» (overage).


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.