Как посчитать Deferred Revenue в SQL
SELECT AVG(salary) FROM employees;?Содержание:
Зачем нужен Deferred Revenue
Deferred Revenue (отложенная выручка) — это деньги, которые компания уже получила, но ещё не заработала. Пока сервис не оказан, предоплата висит на балансе как обязательство перед клиентом, а не как выручка. Аналитику в SaaS этот показатель нужен постоянно: он связывает биллинг (сколько денег пришло) с признанной выручкой (сколько мы реально заработали по правилам учёта), и без него нельзя честно посчитать P&L.
Классический пример. В начале года пользователь платит 1200 рублей за годовую подписку. Признать всю сумму как выручку в январе нельзя — сервис ещё не оказан за февраль–декабрь. Выручку признают равномерно, по 100 рублей в месяц. Значит, в конце января признано 100 рублей, а оставшиеся 1100 — это и есть Deferred Revenue: обязательство «отдать» сервис в течение оставшихся 11 месяцев.
На собеседовании SaaS-аналитика этот вопрос любят, потому что он проверяет сразу две вещи: понимаете ли вы разницу между кассовым и начисленным методом учёта, и умеете ли вы пропорционально «размазать» сумму по времени средствами SQL. Ниже разберём и логику, и рабочие запросы.
Формула
В основе всего одна простая идея — отложенная выручка на любой момент времени равна тому, что заплатили, минус то, что уже признали:
Deferred Revenue на момент t = сумма предоплаты − признанная выручка на момент tПризнанная выручка считается пропорционально прошедшей части оплаченного периода: предоплата × (прошедшие дни / всего дней в периоде). Осталось аккуратно переложить это на SQL с учётом того, что период мог ещё не закончиться, а признанная выручка не должна превышать саму предоплату.
Базовый расчёт
Пусть есть таблица subscriptions(user_id, plan_amount, billed_at, period_start, period_end). Считаем признанную и отложенную выручку по каждой активной подписке на текущую дату:
WITH subs AS (
SELECT
user_id,
plan_amount,
period_start,
period_end,
(period_end - period_start)::NUMERIC AS total_days, -- длина периода в днях
CURRENT_DATE AS as_of
FROM subscriptions
WHERE status = 'active'
)
SELECT
user_id,
plan_amount,
-- Признанная выручка: доля прошедших дней от всего периода × сумма плана.
-- LEAST не даёт признать больше, чем заплатили; GREATEST отсекает отрицательные дни.
LEAST(plan_amount,
plan_amount * GREATEST(0, EXTRACT(EPOCH FROM (LEAST(CURRENT_DATE, period_end) - period_start)) / 86400)::NUMERIC
/ NULLIF(total_days, 0)
) AS revenue_recognized,
-- Отложенная выручка: заплатили минус признали.
plan_amount - LEAST(plan_amount,
plan_amount * GREATEST(0, EXTRACT(EPOCH FROM (LEAST(CURRENT_DATE, period_end) - period_start)) / 86400)::NUMERIC
/ NULLIF(total_days, 0)
) AS deferred_revenue
FROM subs;Здесь три защитных приёма, о которых стоит уметь рассказать на собесе. LEAST(CURRENT_DATE, period_end) не даёт признать выручку за пределами оплаченного периода. GREATEST(0, ...) защищает от подписок, которые ещё не начались (отрицательное число дней). NULLIF(total_days, 0) спасает от деления на ноль, если период вырожденный.
Помесячная амортизация
Часто нужна не текущая точка, а помесячный график признания выручки: сколько выручки «упадёт» в каждый календарный месяц. Для этого раскладываем каждую подписку по месяцам и считаем, сколько её дней приходится на конкретный месяц:
WITH monthly_recognized AS (
SELECT
s.user_id,
s.plan_amount,
s.period_start,
s.period_end,
d.month,
-- Сколько дней подписки попадает в этот месяц: пересечение периода и месяца.
GREATEST(0, EXTRACT(EPOCH FROM (
LEAST(s.period_end, d.month + INTERVAL '1 month')
- GREATEST(s.period_start, d.month)
)) / 86400) AS days_in_month
FROM subscriptions s
CROSS JOIN (
SELECT generate_series('2026-01-01'::DATE, '2026-12-01'::DATE, INTERVAL '1 month')::DATE AS month
) d
WHERE s.status = 'active'
)
SELECT
month,
-- Выручка месяца = сумма плана × доля дней месяца от всей длины периода.
SUM(plan_amount * days_in_month / NULLIF((period_end - period_start)::NUMERIC, 0)) AS recognized_revenue
FROM monthly_recognized
GROUP BY month
ORDER BY month;Логика та же пропорция, только знаменатель — вся длина периода, а числитель — дни, попавшие в конкретный месяц. Пересечение периода и месяца задаётся парой LEAST/GREATEST: берём максимум из начал и минимум из концов. Сумма всех помесячных признаний по одной подписке равна её plan_amount — это удобная проверка корректности запроса.
Общий deferred на дату
Для баланса нужен один агрегат — сколько всего отложенной выручки «висит» на конкретную дату по всем ещё не закрытым подпискам:
WITH all_subs AS (
SELECT
plan_amount,
period_start,
period_end,
GREATEST(0, EXTRACT(EPOCH FROM (CURRENT_DATE - period_start)) / 86400) AS days_passed,
(period_end - period_start)::NUMERIC AS total_days
FROM subscriptions
WHERE status = 'active'
AND period_end > CURRENT_DATE -- берём только периоды, которые ещё не закончились
)
SELECT
SUM(plan_amount) AS total_billed, -- всего выставлено
SUM(LEAST(plan_amount, plan_amount * days_passed / NULLIF(total_days, 0))) AS recognized_so_far, -- уже признано
SUM(plan_amount) - SUM(LEAST(plan_amount, plan_amount * days_passed / NULLIF(total_days, 0))) AS deferred_revenue -- осталось признать
FROM all_subs;Итог: deferred_revenue — та самая цифра, которая идёт на баланс как обязательство. Она всегда лежит между нулём и общей суммой биллинга и уменьшается по мере признания выручки.
Частые ошибки
Ошибка 1. Не учитывать разовые платежи. Setup fees и плата за онбординг признаются по другим правилам, чем подписка. Иногда сразу в момент оплаты, иногда равномерно по периоду. Смешивать их с подпиской в одном пропорциональном расчёте нельзя — выделяйте в отдельную статью.
Ошибка 2. Отмена в середине периода. Пользователь отменил подписку в середине месяца. Признанную выручку за прошедшие дни оставляем как есть, а остаток либо возвращаем, либо списываем с deferred. Игнорировать досрочные отмены — значит завысить будущую выручку.
Ошибка 3. Апгрейд в середине периода. Клиент перешёл на более дорогой тариф до конца оплаченного периода. Нужно пересчитать признание: старую часть по старой цене, новую — по новой, с учётом proration. Забыть про это — исказить и выручку, и deferred.
Ошибка 4. Путать подписку и usage-based биллинг. Подписка — это предоплата, у неё есть отложенная выручка. Оплата по факту потребления (usage-based, pay-as-you-go) признаётся сразу по мере использования, deferred у неё почти нет. Применять к ним одну формулу — грубая ошибка.
Ошибка 5. Кассовый метод вместо начисленного. При кассовом методе выручку признают в момент оплаты, при начисленном — равномерно по периоду. Deferred Revenue существует только в начисленном методе, и именно его используют почти все SaaS-компании. Считать по кассе — значит вообще не иметь отложенной выручки.
Связанные темы
FAQ
Deferred Revenue — это обязательство или актив?
Обязательство (liability). Пока сервис не оказан, деньги, полученные вперёд, отражаются на балансе как обязательство «отдать» клиенту оплаченный сервис. По мере оказания услуги обязательство уменьшается, а сумма переходит в выручку.
Как уменьшается Deferred Revenue?
Каждый период (обычно месяц) часть отложенной выручки признаётся как заработанная и переносится из обязательств в выручку. К концу оплаченного периода вся сумма оказывается признанной, и deferred по этой подписке обнуляется.
Чем различаются годовые и месячные подписки по deferred?
Годовая подписка создаёт большой deferred: клиент заплатил за год вперёд, а признаётся выручка помесячно, поэтому обязательство держится высоким почти весь год. У месячной подписки период короткий, deferred минимальный и быстро обнуляется.
Что делать с deferred при возврате средств?
Уменьшаем отложенную выручку на непризнанную часть и корректируем кассу на сумму возврата. Уже признанная за прошедшие дни выручка обычно остаётся признанной — возвращают, как правило, только неотработанный остаток.
Насколько важен deferred для аудита?
Для публичных компаний — критично. Отложенная выручка — одна из ключевых статей при проверке признания выручки и требований SOX-комплаенса. Ошибки в ней напрямую искажают отчётность, поэтому расчёт должен быть воспроизводим и понятен аудитору.
Тренируйте продуктовую аналитику — откройте тренажёр с 1500+ вопросами для собесов.