Как посчитать Revenue в SQL
Содержание:
Зачем Revenue
«Дай выручку за апрель» — самая частая просьба аналитику. Кажется тривиально, но в e-com / SaaS / маркетплейсе нюансы убивают: оплачено vs доставлено, возвраты, рассрочка, B2B vs B2C, валюта. В одной компании одной и той же метрике могут давать разные числа в разных отчётах.
В статье — формулы, разрезы и нюансы recognition.
Что такое Revenue
Revenue (выручка) — деньги, полученные за товары и услуги за период.
Gross Revenue = Σ (price × quantity)
Net Revenue = Gross - Refunds - DiscountsRecognition: в e-com обычно по дате доставки, в SaaS — по дате выставления счёта, в подписке — pro-rated по дням потребления.
Базовый расчёт
Данные: orders(order_id, total, status, created_at).
SELECT
SUM(total) AS revenue,
COUNT(*) AS orders,
COUNT(DISTINCT user_id) AS buyers
FROM orders
WHERE status IN ('paid', 'delivered')
AND created_at >= '2026-04-01'
AND created_at < '2026-05-01';Важно: status фильтр критичен. pending / cancelled не считаются.
Revenue по разрезам
По месяцам
SELECT
DATE_TRUNC('month', created_at) AS month,
SUM(total) AS revenue,
COUNT(*) AS orders,
SUM(total)::NUMERIC / NULLIF(COUNT(*), 0) AS aov
FROM orders
WHERE status IN ('paid', 'delivered')
GROUP BY 1
ORDER BY 1;По каналу привлечения
SELECT
u.acquisition_channel,
SUM(o.total) AS revenue,
COUNT(DISTINCT o.user_id) AS buyers,
SUM(o.total)::NUMERIC / NULLIF(COUNT(DISTINCT o.user_id), 0) AS rev_per_buyer
FROM orders o
JOIN users u ON u.user_id = o.user_id
WHERE o.status IN ('paid', 'delivered')
AND o.created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY u.acquisition_channel
ORDER BY revenue DESC;Новые vs повторные
WITH user_orders AS (
SELECT
user_id,
total,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS order_num
FROM orders
WHERE status IN ('paid', 'delivered')
)
SELECT
CASE WHEN order_num = 1 THEN 'new' ELSE 'repeat' END AS buyer_type,
SUM(total) AS revenue
FROM user_orders
GROUP BY 1;Gross vs Net Revenue
WITH gross AS (
SELECT SUM(total) AS gross_rev
FROM orders
WHERE status IN ('paid', 'delivered')
AND created_at >= '2026-04-01' AND created_at < '2026-05-01'
),
refunds AS (
SELECT SUM(refund_amount) AS refunded
FROM order_refunds
WHERE refund_date >= '2026-04-01' AND refund_date < '2026-05-01'
),
discounts AS (
SELECT SUM(discount_amount) AS discount
FROM order_discounts
WHERE source = 'platform'
AND created_at >= '2026-04-01' AND created_at < '2026-05-01'
)
SELECT
g.gross_rev,
g.gross_rev - COALESCE(r.refunded, 0) - COALESCE(d.discount, 0) AS net_rev
FROM gross g
CROSS JOIN refunds r
CROSS JOIN discounts d;Revenue recognition
В подписочной модели revenue распределяется по дням потребления:
-- Подписка 12 месяцев $120 = $10/мес = $0,33/день
WITH subs AS (
SELECT
user_id,
amount AS total,
start_date,
end_date,
(amount::NUMERIC / NULLIF(end_date - start_date, 0))::NUMERIC AS daily_rev
FROM subscriptions
WHERE status = 'active'
)
SELECT
DATE_TRUNC('month', g.day) AS month,
SUM(s.daily_rev) AS revenue
FROM generate_series('2026-01-01'::DATE, '2026-12-31'::DATE, INTERVAL '1 day') g(day)
JOIN subs s ON g.day BETWEEN s.start_date AND s.end_date
GROUP BY 1
ORDER BY 1;Частые ошибки
Ошибка 1. Включать pending / cancelled.
Ошибка 2. Игнорировать refunds. Refund вычитают из revenue, но он может прийти через 30 дней.
Ошибка 3. Смешивать валюты.
В мультивалютной системе нужен total × fx_rate_at_date.
Ошибка 4. Recognition в SaaS. $120 за год ≠ $120 revenue в момент оплаты. Распределите по 12 месяцам.
Ошибка 5. Дабл-каунт.
Если refund записан и в refunds таблице, и снижает status заказа — вычитаете дважды.
Ошибка 6. Чрезмерное доверие к одному определению. Управленческий отчёт может использовать Gross Revenue, а финансовый — Net Recognized Revenue. Это не противоречие, а разные ракурсы.
Связанные темы
- Как посчитать GMV в SQL
- Как посчитать AOV в SQL
- Как посчитать MRR в SQL
- Как посчитать unit-economics в SQL
FAQ
Revenue или GMV?
Revenue — заработок компании. GMV — стоимость заказов через платформу. На маркетплейсе разные.
Когда признавать revenue?
E-com: дата доставки. SaaS: дата выставления счёта или потребления. Туризм: дата начала тура. Спросите у финансистов вашу политику.
Refund — это negative revenue?
Да. В управленческой логике refund вычитают из revenue месяца refund (а не месяца покупки).
Скидки — в revenue или нет?
Net Revenue = Gross - скидки. Gross Revenue не учитывает скидки. В отчётах часто показывают обе.
Recognition в подписке — это сложно?
Принципы простые: $120/год = $10/мес. Сложности — досрочное расторжение, апгрейд / даунгрейд тарифа в середине месяца.