MRR-мост в SQL: новые подписки, расширение, сжатие и отток
Месячная повторяющаяся выручка снизилась на 80 тысяч. Это мог быть уход одного клиента или сочетание новых продаж и сокращения крупных аккаунтов. В SQLite построим MRR-мост между двумя снимками: каждое изменение получит тип, а сумма вкладов сойдётся с общим итогом.
MRR отличается от поступления денег
Для примера MRR — месячный эквивалент действующей подписки на дату снимка. Годовая предоплата не становится MRR целиком в месяц платежа. Разовые услуги не включаются. Значения в старом и новом снимке должны быть рассчитаны по одному правилу, иначе мост будет объяснять изменение методики вместо бизнеса.
Единица анализа — аккаунт. Если у него несколько подписок, сначала приведите их к месячному эквиваленту и агрегируйте. Все суммы считаются в одной валюте. В примере снимки содержат только аккаунты с положительным MRR, а отсутствие аккаунта означает ноль действующей повторяющейся выручки на эту дату.
Из двух снимков можно выделить появление, рост, снижение и исчезновение. Чтобы отличить совершенно нового клиента от реактивации, понадобится более длинная история. Поэтому появление в нашем мосте называется new_in_snapshot. Не выдавайте его автоматически за первую продажу за всё время.
Строим полное множество аккаунтов
Сначала UNION собирает ключи обоих снимков без повторов. Два LEFT JOIN добавляют старое и новое значение. Это переносимый способ полного сопоставления, который явно сохраняет ушедших и появившихся. После согласованной трактовки отсутствия как нуля можно рассчитать delta.
Классификация задаётся по значениям: ноль→положительное — появление, положительное→ноль — отток, рост положительного — expansion, снижение — contraction. Равные значения получают unchanged. Сохраняем подписанный вклад: отток и сокращение отрицательны, новые и расширение положительны. Для диаграммы можно дополнительно вывести абсолютный размер, но контрольная сумма использует знак.
WITH previous(customer,mrr) AS (
VALUES ('A',100),('B',200),('C',80)
), current(customer,mrr) AS (
VALUES ('A',150),('B',100),('D',50)
), keys AS (
SELECT customer FROM previous UNION SELECT customer FROM current
), joined AS (
SELECT k.customer,COALESCE(p.mrr,0) AS old_mrr,COALESCE(c.mrr,0) AS new_mrr
FROM keys k LEFT JOIN previous p USING(customer) LEFT JOIN current c USING(customer)
)
SELECT customer,old_mrr,new_mrr,
CASE WHEN old_mrr=0 THEN 'new_in_snapshot'
WHEN new_mrr=0 THEN 'churn'
WHEN new_mrr>old_mrr THEN 'expansion'
WHEN new_mrr<old_mrr THEN 'contraction' ELSE 'unchanged' END AS kind,
new_mrr-old_mrr AS delta
FROM joined ORDER BY customer;Ожидаемые строки результата в порядке столбцов SELECT:
[["A", 100, 150, "expansion", 50], ["B", 200, 100, "contraction", -100], ["C", 80, 0, "churn", -80], ["D", 0, 50, "new_in_snapshot", 50]]Итог и направление работы
Старый MRR равен 380, новый — 300. A дал расширение на 50, B сокращение на 100, C отток на 80, D появление на 50. Вклады складываются в −80. Поэтому общая просадка не означает отсутствие новых продаж: положительные изменения есть, но сокращение и отток больше.
Для команды продаж список expansion показывает, какие аккаунты увеличили потребление. Для клиентского сервиса contraction и churn становятся поводом проверить договор, дату прекращения и причины. Движение в MRR само по себе не объясняет, что произошло: ошибка расчёта тарифа также способна создать ложное сокращение.
Как сохранить сопоставимость месяцев
Фиксируйте момент снимка и правила временной приостановки, скидок и валютной переоценки. Если курс валют меняется, выделите его влияние отдельно или сравнивайте в постоянной валюте. Иначе аккаунт может попасть в expansion без изменения договора.
Для регулярного отчёта группируйте готовые клиентские вклады по kind, сохраняя исходную детализацию. Общая сумма delta должна совпасть с разностью MRR снимков. Не считайте мост по платежам за месяц: годовые предоплаты и задолженность смешают денежный поток с повторяющейся выручкой и изменят смысл всех категорий.
Платёжный расчёт за неполный период — соседняя, но отдельная задача: Подписка на часть месяца: как посчитать пропорциональную сумму в SQL.
Следующий уровень SQL-практики
В «Продвинутом SQL» вы отрабатываете сложные соединения, исторические значения и сверку отчётов. Многошаговые расчёты выполняются на SQLite в браузере с автоматической проверкой на разных данных. Затем вы решаете цельные тестовые задания и собираете витрину интернет-магазина. Откройте программу «Продвинутого SQL».
Продолжить на практике
- Продвинутый SQL: сложные запросы и задачи для собеседований ↗
Продолжение базового SQL: сложные JOIN, оконные рамки, временные ряды, retention, воронки и рекурсивные CTE. Разбирайте примеры и пишите запросы с автоматической проверкой — от отдельных расчётов до витрины интернет-магазина.
