Как посчитать Churn в SQL
Что такое Churn
Churn Rate (отток) — процент пользователей, которые перестали быть активными / отменили подписку за период.
Churn = (Ушедшие за период) / (Активные в начале периода) × 100%Связан с retention: Churn + Retention = 1.
Monthly Churn для подписки
WITH active_start AS (
SELECT COUNT(*) AS active
FROM subscriptions
WHERE status = 'active'
AND started_at <= '2026-03-01'
AND (ended_at IS NULL OR ended_at > '2026-03-01')
),
churned AS (
SELECT COUNT(*) AS cnt
FROM subscriptions
WHERE ended_at >= '2026-03-01' AND ended_at < '2026-04-01'
)
SELECT c.cnt * 1.0 / a.active AS churn_rate
FROM active_start a, churned c;Behavioral Churn (активность, не подписка)
Для продуктов без подписки: пользователь «churned», если не заходил N дней.
-- Churn = не заходил последние 30 дней, хотя раньше заходил
SELECT COUNT(DISTINCT user_id) AS churned
FROM users u
WHERE EXISTS (
SELECT 1 FROM events WHERE user_id = u.user_id
)
AND NOT EXISTS (
SELECT 1 FROM events
WHERE user_id = u.user_id
AND created_at >= CURRENT_DATE - INTERVAL '30 day'
);Voluntary vs Involuntary Churn
Voluntary — пользователь сам отменил
SELECT COUNT(*) AS voluntary
FROM subscriptions
WHERE ended_at BETWEEN '2026-04-01' AND '2026-04-30'
AND cancel_reason = 'user_cancel';Involuntary — карточка не прошла
SELECT COUNT(*) AS involuntary
FROM subscriptions
WHERE ended_at BETWEEN '2026-04-01' AND '2026-04-30'
AND cancel_reason = 'payment_failed';Разделение важно — это разные проблемы с разными решениями.
Churn по когортам
WITH cohort AS (
SELECT user_id, DATE_TRUNC('month', started_at)::DATE AS cohort_month
FROM subscriptions
),
ended AS (
SELECT user_id, DATE_TRUNC('month', ended_at)::DATE AS end_month
FROM subscriptions WHERE ended_at IS NOT NULL
)
SELECT c.cohort_month,
COUNT(*) AS cohort_size,
COUNT(e.user_id) FILTER (
WHERE e.end_month <= c.cohort_month + INTERVAL '3 month'
) AS churned_in_3m,
COUNT(e.user_id) FILTER (
WHERE e.end_month <= c.cohort_month + INTERVAL '3 month'
) * 1.0 / COUNT(*) AS churn_3m
FROM cohort c
LEFT JOIN ended e USING (user_id)
GROUP BY c.cohort_month
ORDER BY c.cohort_month;Прокачать тему на реальных задачах удобно в боте @kariernik_bot — база вопросов собрана с собеседований в Яндексе, Авито, Ozon, Тинькофф.
Monthly Churn Rate по месяцам
WITH monthly AS (
SELECT
DATE_TRUNC('month', dt)::DATE AS month,
COUNT(DISTINCT user_id) FILTER (WHERE event = 'subscribed') AS subs,
COUNT(DISTINCT user_id) FILTER (WHERE event = 'cancelled') AS cancels
FROM events
GROUP BY 1
),
active_start AS (
SELECT month,
SUM(subs - cancels) OVER (ORDER BY month) AS net_active
FROM monthly
)
SELECT m.month, m.subs, m.cancels,
m.cancels * 1.0 / NULLIF(LAG(a.net_active) OVER (ORDER BY m.month), 0) AS churn_rate
FROM monthly m JOIN active_start a USING (month)
ORDER BY m.month;Churn по тарифам
SELECT tariff,
COUNT(*) FILTER (WHERE ended_at IS NOT NULL) * 1.0 / COUNT(*) AS overall_churn
FROM subscriptions
WHERE started_at >= CURRENT_DATE - INTERVAL '6 month'
GROUP BY tariff
ORDER BY overall_churn DESC;Dollar Churn (revenue churn)
Churn в деньгах, не в пользователях:
SELECT
SUM(arpu_lost) AS dollar_churn_amount,
SUM(arpu_lost) * 1.0 / SUM(arpu_start) AS dollar_churn_rate
FROM (
SELECT
CASE WHEN ended_at BETWEEN '2026-04-01' AND '2026-04-30'
THEN monthly_price ELSE 0 END AS arpu_lost,
monthly_price AS arpu_start
FROM subscriptions
WHERE started_at <= '2026-04-01'
) t;Если ушедшие были премиум-тарифами, dollar churn может быть сильно выше user churn.
Churn по причинам (exit survey)
SELECT cancel_reason, COUNT(*) AS cnt,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 2) AS pct
FROM subscriptions
WHERE ended_at >= CURRENT_DATE - INTERVAL '30 day'
GROUP BY cancel_reason
ORDER BY cnt DESC;Ответы пользователей при отмене: «слишком дорого», «не пользуюсь», «конкурент».
Churn в Python
import pandas as pd
subs = pd.read_sql("SELECT * FROM subscriptions", conn)
active_start = (subs['started_at'] <= '2026-04-01').sum()
churned = subs[
(subs['ended_at'] >= '2026-04-01') &
(subs['ended_at'] < '2026-05-01')
].shape[0]
churn_rate = churned / active_startНа собесе такие штуки часто спрашивают. Быстрый способ довести до автоматизма — тренажёр в Telegram с задачами из реальных интервью.
Типичные ошибки
1. Неправильная база для процента
- Правильно: churned / active_at_start_of_period.
- Неправильно: churned / total_users (разные масштабы).
2. Смешивать типы churn
Voluntary + involuntary — разные причины. Анализируйте отдельно.
3. Не учитывать trial
Пользователи на free trial «уходят» естественно. Отделяйте от paid churn.
4. Annual vs Monthly churn
Годовой churn ≠ 12 × месячный. Правильно: (1 - (1 - monthly_churn)^12).
Нормы Churn
Monthly Churn
- B2B SaaS: 1–3%.
- B2C Subscription: 5–10%.
- E-commerce (первая покупка → вторая): 60–80%.
- Mobile games: D30 churn = 70–90% — это норма.
Как снизить Churn
- Involuntary: retry-логика, SMS-напоминания о карте.
- Voluntary: улучшить продукт, onboarding, engagement.
- Price-sensitive: grace период, скидки retention.
- Конкуренты: новые features, коммуникация.
Читайте также
FAQ
Customer churn или revenue churn?
Customer — процент ушедших пользователей. Revenue — процент потерянной выручки. Показывайте оба.
Что важнее — retention или churn?
Одна и та же метрика в разных проекциях. Выберите что удобнее команде (retention — позитив, churn — проблема).
Как предсказать Churn?
ML-модели: логрег, XGBoost, survival analysis. Features: engagement, recency, поведение. Подробнее.
Когда пользователь считается churned?
Зависит от продукта. SaaS: отменил подписку. Приложение: не заходил 30/60/90 дней. E-commerce: не покупал 180 дней. Определяйте по бизнесу.