Как посчитать churn в SQL
LAG(price) OVER (PARTITION BY product_id), чтобы получить «вчерашнюю цену» товара по дням. Почему результат может оказаться неожиданным?Содержание:
Зачем аналитику считать churn в SQL
Churn — метрика, которую ваш CEO смотрит каждый понедельник. В SaaS churn напрямую определяет LTV: LTV = ARPU / churn. Рост churn на 1 пункт может убить unit-экономику. Поэтому умение правильно посчитать churn в SQL — обязательный навык для аналитика подписочного продукта.
Но тут много нюансов. Customer churn ≠ revenue churn. Добровольный отток ≠ вынужденный. Месячный ≠ годовой. Общий churn маскирует когортные проблемы. Если считать «в лоб», легко получить цифру, которая выглядит нормально, но скрывает реальные проблемы.
В статье — готовые SQL-запросы для всех частых случаев:
- Monthly customer churn
- Revenue churn и Net Revenue Retention (NRR)
- Churn по когортам (видно тренд улучшения продукта)
- Voluntary vs involuntary (сколько теряем из-за истёкших карт)
- Пересчёт monthly → annual
Вставьте в Metabase или dbt — будете считать правильно.
Формула
Churn rate = ушли за период / было в началеChurn в разных отраслях
Что считать оттоком и как его мерить, сильно зависит от продукта.
SaaS B2B. Разделяют logo churn (ушедшие клиенты) и revenue churn (потерянная выручка) — они расходятся, если уходят клиенты с нетипичным чеком. Заметную долю составляет involuntary churn из-за сбоев оплаты, и он лечится дуннингом, а не продуктом. При сильном расширении NRR может быть выше 100% даже при ненулевом оттоке.
B2C-подписки. Отток высокий и сезонный, много осознанных отписок. Фокус смещается на реактивацию спящих и перевод пользователей на годовые планы, которые механически снижают месячный churn.
Финтех и банки. Явной «отписки» нет, поэтому «ушёл» определяют поведенчески — например, нет транзакций N месяцев или обнулился баланс. От выбора этого порога сильно зависит цифра, поэтому его фиксируют в определении метрики.
1. Monthly customer churn
WITH monthly AS (
SELECT
user_id,
DATE_TRUNC('month', updated_at) AS month,
status
FROM subscriptions
)
SELECT
month,
COUNT(DISTINCT user_id) FILTER (WHERE status = 'churned')::FLOAT /
NULLIF(COUNT(DISTINCT user_id), 0) AS churn_rate
FROM monthly
GROUP BY month
ORDER BY month;2. Retention-based churn
Churn = 1 - retention. Если в начале месяца было N юзеров, а на конец активны M:
WITH cohort_start AS (
SELECT user_id
FROM users
WHERE signup_at <= '2026-03-01'
AND (churned_at IS NULL OR churned_at > '2026-03-01')
),
cohort_end AS (
SELECT user_id
FROM cohort_start
WHERE user_id IN (
SELECT DISTINCT user_id FROM events WHERE event_at <= '2026-03-31'
)
)
SELECT
1.0 - COUNT(DISTINCT cohort_end.user_id)::FLOAT /
COUNT(DISTINCT cohort_start.user_id) AS churn_rate_march
FROM cohort_start
LEFT JOIN cohort_end USING (user_id);3. Revenue churn
WITH mrr_start AS (
SELECT SUM(mrr) AS mrr_start
FROM subscriptions_snapshot
WHERE snapshot_date = '2026-03-01'
),
mrr_churned AS (
SELECT SUM(s.mrr) AS mrr_lost
FROM subscriptions s
WHERE s.status = 'churned'
AND DATE_TRUNC('month', s.churned_at) = '2026-03-01'
)
SELECT
mrr_lost / mrr_start AS revenue_churn_rate
FROM mrr_start, mrr_churned;4. Net Revenue Retention (NRR)
WITH mrr_movements AS (
SELECT
SUM(CASE WHEN event_type = 'start' THEN mrr ELSE 0 END) AS new_mrr,
SUM(CASE WHEN event_type = 'expansion' THEN mrr ELSE 0 END) AS expansion,
SUM(CASE WHEN event_type = 'contraction' THEN mrr ELSE 0 END) AS contraction,
SUM(CASE WHEN event_type = 'churn' THEN mrr ELSE 0 END) AS churn
FROM mrr_events
WHERE DATE_TRUNC('month', event_at) = '2026-03-01'
),
mrr_start AS (
SELECT SUM(mrr) AS start_mrr FROM subs WHERE snapshot_date = '2026-03-01'
)
SELECT
(start_mrr + expansion - contraction - churn) / start_mrr AS nrr
FROM mrr_movements, mrr_start;NRR > 1 — рост даже без новых.
5. Churn по когортам
WITH cohorts AS (
SELECT user_id, DATE_TRUNC('month', signup_at) AS cohort
FROM users
),
activity AS (
SELECT DISTINCT user_id, DATE_TRUNC('month', event_at) AS activity_month
FROM events
)
SELECT
c.cohort,
a.activity_month,
-- общее число месяцев между cohort и activity_month
(EXTRACT(YEAR FROM AGE(a.activity_month, c.cohort)) * 12
+ EXTRACT(MONTH FROM AGE(a.activity_month, c.cohort)))::INT AS months_since,
COUNT(DISTINCT a.user_id) AS active,
COUNT(DISTINCT a.user_id)::FLOAT /
(SELECT COUNT(*) FROM cohorts WHERE cohort = c.cohort) AS retention
FROM cohorts c
LEFT JOIN activity a ON a.user_id = c.user_id
GROUP BY c.cohort, a.activity_month
ORDER BY c.cohort, a.activity_month;6. Voluntary vs involuntary churn
SELECT
CASE
WHEN churn_reason IN ('card_expired', 'payment_failed') THEN 'involuntary'
ELSE 'voluntary'
END AS churn_type,
COUNT(*) AS churned
FROM subscriptions
WHERE status = 'churned'
AND DATE_TRUNC('month', churned_at) = '2026-03-01'
GROUP BY 1;7. Monthly vs annual churn
Месячный churn 5% — это не 60% в год:
-- monthly compound → annual
SELECT 1 - POWER(1 - 0.05, 12) AS annual_churn;
-- ≈ 0.46 = 46%Примеры с собеседований
Churn на собесе проверяют на аккуратности определений.
«В чём разница user churn и revenue churn?» Сильный ответ: user churn считает долю ушедших клиентов, revenue churn — долю потерянной выручки; они расходятся, когда уходят «дорогие» или «дешёвые» клиенты непропорционально. Для unit-экономики важнее revenue churn.
«Что такое negative churn и почему это хорошо?» Сильный ответ: это когда расширение существующих клиентов перекрывает потери, то есть NRR выше 100% — выручка базы растёт без единого нового клиента. Признак сильного продукта с работающим апселлом.
«Monthly churn 5% — это сколько в год?» Не 60%. Отток компаундится: 1 − (1 − 0.05)¹² ≈ 46%. Линейное умножение на 12 — типичная ошибка. Прогнать такие вопросы с разбором можно в Карьернике.
Частые ошибки
Считать только по активным. Если базой взять тех, кто дожил до конца периода, получается survivorship bias — ушедшие выпадают из знаменателя, и churn занижается. База — это «кто мог уйти», то есть активные на начало периода.
Путать customer и revenue churn. Это разные метрики — доля ушедших клиентов и доля потерянной выручки. Для решений об unit-экономике берут revenue churn: один customer churn может скрывать, что ушли именно крупные клиенты.
Смешивать monthly и annual без пересчёта. Месячный и годовой churn нельзя сравнивать напрямую — отток компаундится, и 5% в месяц это около 46% в год, а не 60%.
Забывать про involuntary churn. Часть оттока — это не осознанный уход, а сбой оплаты из-за истёкшей карты. Его лечат дуннингом и ретраями платежей, и смешивать его с добровольным churn нельзя, иначе продуктовые выводы будут неверными.
Не учитывать когорты. Общий churn маскирует когортные проблемы: свежие слабые когорты тонут, пока средняя цифра держится за счёт старых. Разрез по когортам показывает реальный тренд.
Связанные темы
FAQ
Customer или revenue churn — что считать?
Обе метрики, но для разных задач. Revenue churn нужен для unit-экономики и оценки влияния на выручку, customer churn — для общей картины удержания. Они расходятся, когда уходят клиенты с нетипичным чеком, поэтому одну без другой смотреть рискованно.
Как выбрать период — monthly или annual?
Зависит от цикла оплаты: для SaaS с месячной подпиской естественен monthly churn, для enterprise с годовыми контрактами — annual. Главное — не сравнивать их напрямую: из-за компаундинга 5% в месяц дают около 46% в год.
Как определить churn там, где нет явной отписки?
Через поведенческий признак: пользователь считается ушедшим, если N месяцев нет активности или транзакций. Порог фиксируют в определении метрики, потому что от него напрямую зависит цифра оттока.
Как связаны churn и LTV?
Напрямую: для подписки LTV ≈ ARPU / churn, срок жизни клиента обратно пропорционален оттоку. Снижение churn с 5% до 3% в месяц увеличивает LTV почти вдвое — поэтому работа с оттоком часто даёт больше, чем рост привлечения.