Как посчитать ARPU в SQL
payments(user_id) и refunds(user_id). Нужно получить пользователей, у которых был платёж, но не было возврата. Какой вариант корректнее всего описывает задачу?Содержание:
Зачем аналитику считать ARPU в SQL
ARPU — одна из базовых метрик продукта с монетизацией: от него зависит, что мы пишем в юнит-экономике, как выглядит LTV и что показываем инвесторам. В SQL формула простая, но ошибиться в знаменателе — привычное дело: кого именно считать «пользователем» (зарегистрированных, активных, платящих) и за какой период.
В этой статье — формулы, готовые запросы и декомпозиция через paying rate. Дальше — разрезы по сегментам, динамика и частые ошибки.
Формулы ARPU и ARPPU
ARPU (Average Revenue Per User) — выручка на пользователя:
ARPU = Revenue / Total UsersARPPU (Average Revenue Per Paying User) — выручка на платящего:
ARPPU = Revenue / Paying UsersСвязь двух метрик:
ARPU = Paying Rate × ARPPUПолезно держать в голове: ARPU падает либо из-за снижения доли платящих, либо из-за падения среднего чека у платящих. Декомпозиция позволяет понять, где именно просела выручка.
Схема данных
В примерах используем три таблицы:
users (user_id, registered_at, channel, platform)
orders (user_id, amount, status, created_at)
events (user_id, event, event_at)1. ARPU за месяц
SELECT
SUM(o.amount)::NUMERIC AS revenue,
COUNT(DISTINCT u.user_id) AS total_users,
SUM(o.amount)::NUMERIC / NULLIF(COUNT(DISTINCT u.user_id), 0) AS arpu
FROM users u
LEFT JOIN orders o
ON o.user_id = u.user_id
AND o.status = 'paid'
AND o.created_at >= '2026-04-01'
AND o.created_at < '2026-05-01'
WHERE u.registered_at < '2026-05-01';Два важных момента:
- LEFT JOIN, чтобы пользователи без заказов остались в знаменателе.
NULLIF(..., 0)— чтобы запрос не падал, если в периоде нет пользователей.
2. ARPPU за месяц
Знаменатель — только платящие:
SELECT
SUM(amount)::NUMERIC AS revenue,
COUNT(DISTINCT user_id) AS paying_users,
SUM(amount)::NUMERIC / NULLIF(COUNT(DISTINCT user_id), 0) AS arppu
FROM orders
WHERE status = 'paid'
AND created_at >= '2026-04-01'
AND created_at < '2026-05-01';ARPPU почти всегда значительно выше ARPU — он измеряет средний чек платящих и не «размывается» нулями от бесплатной аудитории.
3. ARPU и ARPPU вместе — декомпозиция
Одним запросом видно обе метрики и paying rate:
WITH monthly AS (
SELECT
DATE_TRUNC('month', created_at)::DATE AS month,
COUNT(DISTINCT user_id) AS total_users,
COUNT(DISTINCT user_id) FILTER (WHERE status = 'paid') AS paying_users,
SUM(amount) FILTER (WHERE status = 'paid') AS revenue
FROM orders
GROUP BY 1
)
SELECT
month,
revenue,
total_users,
paying_users,
revenue::NUMERIC / NULLIF(total_users, 0) AS arpu,
paying_users::NUMERIC / NULLIF(total_users, 0) AS paying_rate,
revenue::NUMERIC / NULLIF(paying_users, 0) AS arppu
FROM monthly
ORDER BY month;Если ARPU упал, достаточно посмотреть на paying rate и ARPPU, чтобы понять, какая из двух сил потянула метрику вниз.
4. ARPU по каналам
SELECT
u.channel,
SUM(o.amount)::NUMERIC / NULLIF(COUNT(DISTINCT u.user_id), 0) AS arpu
FROM users u
LEFT JOIN orders o
ON o.user_id = u.user_id
AND o.status = 'paid'
AND o.created_at >= '2026-04-01'
AND o.created_at < '2026-05-01'
WHERE u.registered_at < '2026-04-01'
GROUP BY u.channel
ORDER BY arpu DESC;Два типичных наблюдения: органика часто даёт более высокий ARPU, чем платный трафик, а длинные когорты ведут себя лучше свежих — поэтому при сравнении каналов полезно зафиксировать срок регистрации.
5. ARPU по платформам
SELECT
u.platform,
SUM(o.amount)::NUMERIC / NULLIF(COUNT(DISTINCT u.user_id), 0) AS arpu
FROM users u
LEFT JOIN orders o
ON o.user_id = u.user_id
AND o.status = 'paid'
AND o.created_at >= '2026-04-01'
AND o.created_at < '2026-05-01'
GROUP BY u.platform
ORDER BY arpu DESC;6. ARPU по когорте
WITH cohort AS (
SELECT
user_id,
DATE_TRUNC('month', MIN(created_at))::DATE AS cohort_month
FROM orders
WHERE status = 'paid'
GROUP BY user_id
)
SELECT
c.cohort_month,
SUM(o.amount)::NUMERIC / NULLIF(COUNT(DISTINCT c.user_id), 0) AS arpu_cohort
FROM cohort c
LEFT JOIN orders o
ON o.user_id = c.user_id
AND o.status = 'paid'
GROUP BY c.cohort_month
ORDER BY c.cohort_month;Когортный ARPU показывает, как меняется выручка на «старого» пользователя: в здоровом продукте более старые когорты постепенно накапливают большую сумму, чем свежие.
7. MoM-динамика ARPU
WITH monthly_arpu AS (
SELECT
DATE_TRUNC('month', o.created_at)::DATE AS month,
SUM(o.amount)::NUMERIC / NULLIF(COUNT(DISTINCT u.user_id), 0) AS arpu
FROM orders o
JOIN users u USING (user_id)
WHERE o.status = 'paid'
GROUP BY 1
)
SELECT
month,
arpu,
LAG(arpu) OVER (ORDER BY month) AS prev_arpu,
ROUND(100.0 * (arpu - LAG(arpu) OVER (ORDER BY month))
/ NULLIF(LAG(arpu) OVER (ORDER BY month), 0), 2) AS mom_pct
FROM monthly_arpu
ORDER BY month;Быстрая диагностика «упал / вырос на сколько» без необходимости переключаться в BI.
8. Net ARPU с учётом возвратов
SELECT
DATE_TRUNC('month', o.created_at)::DATE AS month,
SUM(o.amount - COALESCE(o.refund_amount, 0))::NUMERIC
/ NULLIF(COUNT(DISTINCT u.user_id), 0) AS net_arpu
FROM orders o
JOIN users u USING (user_id)
WHERE o.status = 'paid'
GROUP BY 1
ORDER BY 1;Если в отчёте не вычесть возвраты, ARPU будет систематически завышен — особенно в категориях с высоким refund rate.
Частые ошибки
1. Целочисленное деление
В Postgres SUM(int) / COUNT(int) может сработать как целочисленное деление. Приводите одну из сторон к NUMERIC (::NUMERIC или 1.0 * ...).
2. Разный знаменатель — разная метрика
«Все зарегистрированные» ≠ «активные в месяце» ≠ «платящие». Один и тот же бизнес на одних и тех же данных даст три разных ARPU. Решите заранее, какой знаменатель входит в определение метрики, и зафиксируйте его в документации.
3. Mix-эффект
ARPU может упасть не потому, что что-то ухудшилось, а потому, что изменился состав трафика — например, пришёл поток новых пользователей с низким ARPU. Полезно смотреть ARPU в разрезе каналов и когорт, а не только общую цифру.
4. Возвраты и скидки
Если считать SUM(amount) по всем paid-заказам, возвраты в расчёт не попадают и метрика завышается. Используйте SUM(amount - refund_amount) или добавляйте исключение возвращённых заказов.
5. ARPU — не LTV
ARPU — выручка на пользователя за период (обычно месяц). LTV — выручка за всё время жизни. Для подписочных продуктов приблизительно LTV ≈ ARPU / churn_rate, но это именно грубая оценка — см. как посчитать LTV в SQL.
Python-аналог
Быстрая проверка цифр из ноутбука:
revenue = orders.loc[orders['status'] == 'paid', 'amount'].sum()
total_users = users['user_id'].nunique()
arpu = revenue / max(total_users, 1)Связанные темы
- ARPU простыми словами
- ARPU vs ARPPU
- Как посчитать LTV в SQL
- Unit-экономика простыми словами
- Кейс: ARPU упал
FAQ
ARPU или ARPPU — что показывать PM?
Обычно обе. ARPU — для общей картины и отчётности, ARPPU — для оценки продажной части и тарифной политики.
Как считать ARPU с разовыми покупками?
Revenue за период делим на пользователей за тот же период. Пользователь с несколькими покупками учитывается в знаменателе один раз (поэтому COUNT(DISTINCT user_id)).
ARPU растёт, а orders падают — это хорошо?
Не всегда. Если активные пользователи платят больше — да. Если просто ушла аудитория с небольшими чеками, total revenue мог упасть сильнее, чем вырос средний. Смотрите и ARPU, и total revenue одновременно.
Нужно ли чистить ARPU от скидок?
Да, разумно хранить и gross, и net ARPU. В gross видно «ценник», в net — фактическое поступление денег.