Как посчитать churn в SQL
WHERE country IN ('RU', 'KZ') для строки, где country = NULL?Содержание:
Что такое churn
Churn rate — доля пользователей, переставших быть активными (в подписке — отменивших её) за период:
Churn = ушедшие за период / активные в начале периода × 100%Связан с удержанием напрямую: Churn + Retention = 100%.
В статье — готовые SQL-запросы:
- базовый monthly customer churn;
- behavioral churn для продуктов без подписки;
- voluntary vs involuntary;
- когортный churn;
- churn по тарифам;
- dollar churn;
- ошибки и ориентиры.
1. Monthly customer churn для подписки
WITH active_start AS (
SELECT COUNT(*) AS active
FROM subscriptions
WHERE 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::NUMERIC / NULLIF(a.active, 0) AS churn_rate
FROM active_start a, churned c;Знаменатель — «активные в начале периода», не «все, кто когда-либо был». Это принципиально: смешав эти две базы, получите сильно заниженный churn.
2. Behavioral churn (для продуктов без подписки)
Пользователь считается ушедшим, если давно не заходил:
SELECT COUNT(DISTINCT u.user_id) AS churned
FROM users u
WHERE EXISTS (
SELECT 1 FROM events e WHERE e.user_id = u.user_id
)
AND NOT EXISTS (
SELECT 1
FROM events e
WHERE e.user_id = u.user_id
AND e.event_at >= CURRENT_DATE - INTERVAL '30 day'
);Окно «30 дней» — пример. Конкретный срок зависит от продукта: для ежедневного сервиса 30 дней это уже долго, для сезонного продукта — может быть нормально.
3. Voluntary vs involuntary
-- voluntary: пользователь сам отменил подписку
SELECT COUNT(*) AS voluntary
FROM subscriptions
WHERE ended_at >= '2026-04-01'
AND ended_at < '2026-05-01'
AND cancel_reason = 'user_cancel';
-- involuntary: не прошла оплата, карта истекла и т.п.
SELECT COUNT(*) AS involuntary
FROM subscriptions
WHERE ended_at >= '2026-04-01'
AND ended_at < '2026-05-01'
AND cancel_reason IN ('payment_failed', 'card_expired');Это разные проблемы: voluntary обычно решается продуктом и коммуникацией, involuntary — технически: retry-логика, обновление карт, SMS-напоминания.
4. Когортный 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'
)::NUMERIC
/ NULLIF(COUNT(*), 0) AS churn_3m
FROM cohort c
LEFT JOIN ended e USING (user_id)
GROUP BY c.cohort_month
ORDER BY c.cohort_month;Когортный разрез нужен, чтобы увидеть, улучшается ли retention в свежих когортах. Если у новых когорт churn ниже — значит, продукт становится лучше.
5. Churn по тарифам
SELECT
tariff,
COUNT(*) AS cohort_size,
COUNT(*) FILTER (WHERE ended_at IS NOT NULL) AS churned,
COUNT(*) FILTER (WHERE ended_at IS NOT NULL)::NUMERIC
/ NULLIF(COUNT(*), 0) AS overall_churn
FROM subscriptions
WHERE started_at >= CURRENT_DATE - INTERVAL '6 month'
GROUP BY tariff
ORDER BY overall_churn DESC;Часто у бюджетных тарифов churn в разы выше, чем у дорогих — это надо знать до того, как принимать решения о скидках.
6. Dollar churn (revenue churn)
Потерянная выручка, а не пользователи:
WITH mrr_start AS (
SELECT SUM(monthly_price) AS mrr_start
FROM subscriptions
WHERE started_at <= '2026-04-01'
AND (ended_at IS NULL OR ended_at > '2026-04-01')
),
mrr_churned AS (
SELECT SUM(monthly_price) AS mrr_lost
FROM subscriptions
WHERE ended_at >= '2026-04-01'
AND ended_at < '2026-05-01'
)
SELECT
mrr_churned.mrr_lost,
mrr_start.mrr_start,
mrr_churned.mrr_lost::NUMERIC / NULLIF(mrr_start.mrr_start, 0) AS dollar_churn_rate
FROM mrr_start, mrr_churned;Если уходят в основном дорогие тарифы, dollar churn существенно выше пользовательского — и именно он определяет финансовый эффект.
7. Причины отмены
SELECT
cancel_reason,
COUNT(*) AS cnt,
ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER (), 2) AS pct
FROM subscriptions
WHERE ended_at >= CURRENT_DATE - INTERVAL '30 day'
GROUP BY cancel_reason
ORDER BY cnt DESC;Если причины собираются через exit-анкету, отчёт сразу показывает, куда бить в первую очередь: цены, UX, продуктовая ценность или технические проблемы.
Частые ошибки
1. Неправильная база знаменателя
ушедшие / всех когда-либо занижает churn. Правильная база — активные в начале периода.
2. Смешивать voluntary и involuntary
Это разные процессы и разные команды должны ими заниматься. Разделяйте.
3. Не учитывать триалы
Пользователи, не сконвертировавшиеся из trial, часто попадают в «ушедших». Это искажает картину paid churn — их стоит выделять отдельно.
4. Annual churn ≠ 12 × monthly
Правильная формула пересчёта при условии одинакового monthly churn:
annual_churn = 1 − (1 − monthly_churn)^12Например, monthly 5% — это annual ≈ 46%, а не 60%.
Churn в Python
subs = pd.read_sql('SELECT * FROM subscriptions', conn)
active_start = (
(subs['started_at'] <= '2026-04-01') &
((subs['ended_at'].isna()) | (subs['ended_at'] > '2026-04-01'))
).sum()
churned = (
(subs['ended_at'] >= '2026-04-01') &
(subs['ended_at'] < '2026-05-01')
).sum()
churn_rate = churned / max(active_start, 1)Связанные темы
FAQ
Customer или revenue churn?
Customer — доля ушедших пользователей. Revenue — доля потерянной выручки. В отчёте лучше показывать оба: customer даёт понимание людей, revenue — финансовый эффект.
Что важнее — retention или churn?
Одна и та же метрика в разных проекциях. Выбирайте ту, с которой удобнее команде: retention звучит как позитивная метрика (её растят), churn — как проблема (её снижают).
Когда пользователь считается churned в продукте без подписки?
Зависит от продукта. Для приложения с ежедневным использованием — 14–30 дней без активности. Для редко используемых сервисов срок может быть 90 дней и больше. Главное — зафиксировать определение и не менять его задним числом.
Как снижать churn?
Короткий чек-лист: retry-логика для involuntary, улучшение onboarding и ценности продукта для voluntary, коммуникации «при риске» на основе модели предсказания, retention-акции для ценных клиентов.