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

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

Зачем MRR Churn

В SaaS-компании отвалилось 5% клиентов за месяц — кажется норма. Но revenue упала на 15%. Аналитик смотрит: отвалились enterprise-клиенты с высоким чеком, остались мелкие. Customer churn 5%, MRR churn 15%. Реальная проблема видна только через revenue-метрику.

MRR Churn — главная метрика SaaS-бизнесов. В статье — SQL и нюансы (gross vs net, customer vs revenue).

Что такое MRR Churn

MRR Churn Rate — доля MRR, потерянная за период (отказы, downgrade).

Gross MRR Churn (%) = lost MRR / MRR at start × 100%
Net MRR Churn (%)   = (lost MRR - new expansion MRR) / MRR at start × 100%

Net Churn может быть отрицательным — это «negative churn», когда expansion компенсирует отток. Это золотой стандарт SaaS.

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

Данные: subscriptions(user_id, mrr, started_at, ended_at, status).

WITH start_mrr AS (
    SELECT SUM(mrr) AS mrr_start
    FROM subscriptions
    WHERE started_at <= '2026-04-01'
      AND (ended_at IS NULL OR ended_at >= '2026-04-01')
),
churned_mrr AS (
    SELECT SUM(mrr) AS mrr_churned
    FROM subscriptions
    WHERE ended_at >= '2026-04-01' AND ended_at < '2026-05-01'
)
SELECT
    s.mrr_start,
    c.mrr_churned,
    c.mrr_churned::NUMERIC / NULLIF(s.mrr_start, 0) * 100 AS gross_mrr_churn_pct
FROM start_mrr s
CROSS JOIN churned_mrr c;

Важно: churn считается на начало периода, не на конец. Иначе будете занижать.

Gross vs Net Churn

WITH start_mrr AS (
    SELECT SUM(mrr) AS mrr_start
    FROM subscriptions
    WHERE started_at <= '2026-04-01'
      AND (ended_at IS NULL OR ended_at >= '2026-04-01')
),
churned AS (
    SELECT SUM(mrr) AS mrr_lost
    FROM subscriptions
    WHERE ended_at >= '2026-04-01' AND ended_at < '2026-05-01'
),
expansion AS (
    SELECT SUM(mrr_delta) AS mrr_gained
    FROM subscription_upgrades
    WHERE upgrade_date >= '2026-04-01' AND upgrade_date < '2026-05-01'
      AND mrr_delta > 0
),
contraction AS (
    SELECT SUM(ABS(mrr_delta)) AS mrr_lost_downgrade
    FROM subscription_upgrades
    WHERE upgrade_date >= '2026-04-01' AND upgrade_date < '2026-05-01'
      AND mrr_delta < 0
)
SELECT
    s.mrr_start,
    c.mrr_lost + ct.mrr_lost_downgrade AS total_lost,
    e.mrr_gained,
    (c.mrr_lost + ct.mrr_lost_downgrade)::NUMERIC / NULLIF(s.mrr_start, 0) * 100 AS gross_churn_pct,
    (c.mrr_lost + ct.mrr_lost_downgrade - e.mrr_gained)::NUMERIC / NULLIF(s.mrr_start, 0) * 100 AS net_churn_pct
FROM start_mrr s
CROSS JOIN churned c
CROSS JOIN expansion e
CROSS JOIN contraction ct;
Закрепи формулу mrr churn в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать mrr churn в Telegram

Customer vs Revenue Churn

WITH start_state AS (
    SELECT
        COUNT(*) AS customers_start,
        SUM(mrr) AS mrr_start
    FROM subscriptions
    WHERE started_at <= '2026-04-01'
      AND (ended_at IS NULL OR ended_at >= '2026-04-01')
),
churned AS (
    SELECT
        COUNT(*) AS customers_lost,
        SUM(mrr) AS mrr_lost
    FROM subscriptions
    WHERE ended_at >= '2026-04-01' AND ended_at < '2026-05-01'
)
SELECT
    s.customers_start,
    s.mrr_start,
    c.customers_lost,
    c.mrr_lost,
    c.customers_lost::NUMERIC * 100 / NULLIF(s.customers_start, 0) AS customer_churn_pct,
    c.mrr_lost::NUMERIC * 100 / NULLIF(s.mrr_start, 0) AS mrr_churn_pct
FROM start_state s
CROSS JOIN churned c;

Customer churn ≠ MRR churn. Если уходят высокотарифные — MRR churn выше.

MRR Churn по сегментам

WITH start_mrr_seg AS (
    SELECT plan_type, SUM(mrr) AS mrr_start
    FROM subscriptions
    WHERE started_at <= '2026-04-01'
      AND (ended_at IS NULL OR ended_at >= '2026-04-01')
    GROUP BY plan_type
),
churned_seg AS (
    SELECT plan_type, SUM(mrr) AS mrr_lost
    FROM subscriptions
    WHERE ended_at >= '2026-04-01' AND ended_at < '2026-05-01'
    GROUP BY plan_type
)
SELECT
    s.plan_type,
    s.mrr_start,
    COALESCE(c.mrr_lost, 0) AS mrr_lost,
    COALESCE(c.mrr_lost, 0)::NUMERIC * 100 / NULLIF(s.mrr_start, 0) AS churn_pct
FROM start_mrr_seg s
LEFT JOIN churned_seg c ON c.plan_type = s.plan_type
ORDER BY churn_pct DESC;

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

Ошибка 1. Считать на начало или на конец? Только на начало периода. На конец будет иное.

Ошибка 2. Включать новых, которые в этом же месяце отвалились. Если клиент пришёл 3 апреля и ушёл 25 апреля — это churn? В стандартной логике — нет, он не был в MRR на начало. Но «micro-churn» нужно отдельно отслеживать.

Ошибка 3. Расчёт annualised. Monthly churn 3% × 12 ≠ 36% annual. Правильно: 1 - (1-monthly)^12 = 1 - 0,97^12 ≈ 30,6%.

Ошибка 4. Игнорировать downgrade. Customer не ушёл, перешёл с $100 на $30 — это $70 contraction MRR.

Ошибка 5. NULL в ended_at. Active subscription имеет ended_at IS NULL. Не путать с пропавшими данными.

Ошибка 6. Не различать voluntary vs involuntary churn. Voluntary — клиент сам отписался. Involuntary — карта не прошла платёж. Разные причины, разный fix.

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

FAQ

Какой MRR Churn считается хорошим?

В SaaS B2B: 0,5-1% monthly = отлично, 2-3% = средне, 5%+ = проблема. В SaaS B2C: 3-7% — норма.

Net Churn может быть отрицательным?

Да, и это лучшее, что может быть. Negative net churn значит, что expansion (рост существующих) превышает отток.

Когда smотреть customer, когда MRR churn?

Customer — про retention клиента. MRR — про revenue. В энтерпрайзе MRR важнее.

Voluntary vs involuntary?

Voluntary — клиент сам ушёл (плохой fit, переход к конкуренту). Involuntary — техническая (карта не прошла, банк блокировал). Решаются разными командами.

Annual MRR churn = monthly × 12?

Нет. Annual = 1 - (1-monthly)^12. Для 3% monthly = 30,6% annual, не 36%.