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

Проверь себя · 1/3разбор после ответа
Вы хотите вывести только те категории товаров, где суммарные продажи больше 10000. Таблица sales с колонками category и amount. Какой запрос корректен?

Зачем Revenue

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

В статье — формулы, разрезы и нюансы признания выручки.

Что такое Revenue

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

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

Признание выручки: в e-com обычно по дате доставки, в SaaS — по дате выставления счёта, в подписке — пропорционально дням потребления.

Базовый расчёт

Данные: 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

В подписочной модели выручка распределяется по дням потребления:

-- Подписка 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. Игнорировать возвраты. Возврат вычитают из выручки, но он может прийти через 30 дней.

Ошибка 3. Смешивать валюты. В мультивалютной системе нужен total × fx_rate_at_date.

Ошибка 4. Признание выручки в SaaS. $120 за год ≠ $120 выручки в момент оплаты. Распределите по 12 месяцам.

Ошибка 5. Двойной учёт. Если возврат записан и в refunds таблице, и снижает status заказа — вычитаете дважды.

Ошибка 6. Чрезмерное доверие к одному определению. Управленческий отчёт может использовать Gross Revenue, а финансовый — Net Recognized Revenue. Это не противоречие, а разные ракурсы.

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

FAQ

Revenue или GMV?

Revenue — это то, что заработала сама компания. GMV — суммарная стоимость всех заказов, прошедших через платформу. На маркетплейсе это разные числа: GMV считает весь оборот покупателей, а revenue — только комиссию площадки.

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

Момент признания зависит от бизнеса: в e-com выручку обычно признают по дате доставки, в SaaS — по дате выставления счёта или потребления, в туризме — по дате начала тура. Единого правила нет, поэтому политику признания уточните у финансистов вашей компании.

Возврат — это отрицательная выручка?

По сути да: возврат уменьшает выручку. В управленческой логике его вычитают из выручки того месяца, когда возврат произошёл, а не месяца исходной покупки — поэтому крупные возвраты могут просадить свежий месяц.

Скидки входят в выручку или нет?

Смотря какую выручку считать: Net Revenue = Gross Revenue − скидки, то есть чистая выручка скидки вычитает, а Gross Revenue их игнорирует. В отчётах обычно показывают обе цифры, чтобы было видно, сколько съедают промо и скидки.

Сложно ли признание выручки в подписке?

Базовый принцип простой: $120 за год признаются как $10 в месяц. Сложности начинаются на краях — досрочное расторжение, апгрейд или даунгрейд тарифа в середине месяца, паузы подписки. Именно эти случаи и приходится аккуратно разносить по дням.