Как посчитать Revenue в SQL

Закрепи формулу revenue в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать revenue в Telegram

Зачем Revenue

«Дай выручку за апрель» — самая частая просьба аналитику. Кажется тривиально, но в e-com / SaaS / маркетплейсе нюансы убивают: оплачено vs доставлено, возвраты, рассрочка, B2B vs B2C, валюта. В одной компании одной и той же метрике могут давать разные числа в разных отчётах.

В статье — формулы, разрезы и нюансы recognition.

Что такое Revenue

Revenue (выручка) — деньги, полученные за товары и услуги за период.

Gross Revenue = Σ (price × quantity)
Net Revenue   = Gross - Refunds - Discounts

Recognition: в 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;
Закрепи формулу revenue в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать revenue в Telegram

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. Это не противоречие, а разные ракурсы.

Связанные темы

FAQ

Revenue или GMV?

Revenue — заработок компании. GMV — стоимость заказов через платформу. На маркетплейсе разные.

Когда признавать revenue?

E-com: дата доставки. SaaS: дата выставления счёта или потребления. Туризм: дата начала тура. Спросите у финансистов вашу политику.

Refund — это negative revenue?

Да. В управленческой логике refund вычитают из revenue месяца refund (а не месяца покупки).

Скидки — в revenue или нет?

Net Revenue = Gross - скидки. Gross Revenue не учитывает скидки. В отчётах часто показывают обе.

Recognition в подписке — это сложно?

Принципы простые: $120/год = $10/мес. Сложности — досрочное расторжение, апгрейд / даунгрейд тарифа в середине месяца.