Как посчитать AOV в SQL
Содержание:
Зачем аналитику уметь считать AOV в SQL
AOV — одна из трёх опор выручки в e-commerce: Revenue = Traffic × CR × AOV. Если трафик вырос, а выручка нет — проверьте AOV. Если AOV стабилен, а выручка упала — смотрите конверсию. Поэтому аналитика в e-com или маркетплейсе постоянно просят: «А какой AOV по категориям в этом месяце?», «Сравни новых и повторных», «Построй динамику по когортам».
Простое AVG(total) работает, но обманывает. Один «кит» с чеком на миллион может двинуть среднее на 30% без реальных изменений в поведении пользователей. Поэтому грамотный аналитик никогда не показывает AOV без медианы, P95 и разбиения по сегментам.
В статье — готовые SQL-запросы для дашборда и разового анализа:
- Общий AOV + медиана + P95 (устойчиво к китам)
- AOV по месяцам, категориям, каналам
- AOV новых и повторных покупателей (повторные обычно платят больше)
- AOV по дециль-сегментам (RFM-лайт)
- AOV + частота покупок → выручка на покупателя
- MoM-изменение через
LAG()
Данные: orders(order_id, user_id, total, status, created_at, category).
Что такое AOV
AOV (Average Order Value) — средний чек заказа.
AOV = Total revenue / Total ordersОдна из ключевых метрик в e-commerce, маркетплейсах, сервисах доставки.
AOV в разных отраслях
Как растят и читают средний чек, зависит от типа продукта.
Классический e-commerce. AOV поднимают апселлом, кросс-селлом, бандлами и порогом бесплатной доставки — «добери до 3000 ₽». Распределение с тяжёлым хвостом, поэтому рядом со средним всегда смотрят медиану.
Маркетплейс. AOV сильно зависит от микса категорий: электроника задаёт высокий чек, товары повседневного спроса — низкий. Общий AOV без разреза по категориям почти бесполезен.
Доставка еды. AOV — ключевой рычаг юнит-экономики, потому что стоимость доставки почти фиксированная: чем выше чек, тем лучше окупается курьер. Отсюда минимальная сумма заказа и бесплатная доставка от порога.
Подписочные боксы. AOV зафиксирован тарифом, поэтому фокус смещается с чека на частоту и удержание.
Схема данных
orders (order_id, user_id, total, status, created_at)1. Общий AOV
SELECT
COUNT(*) AS orders_cnt,
SUM(total) AS revenue,
AVG(total) AS aov
FROM orders
WHERE status = 'paid'
AND created_at >= '2026-01-01';Важно: фильтр по status = 'paid' — не включать отменённые.
2. AOV с медианой
AOV чувствительно к выбросам (один большой заказ искажает среднее). Медиана устойчива:
SELECT
AVG(total) AS aov_mean,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total) AS aov_median,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY total) AS p95
FROM orders
WHERE status = 'paid';Если AVG >> median — есть тяжёлый хвост (киты).
3. AOV по месяцам
SELECT
DATE_TRUNC('month', created_at) AS month,
COUNT(*) AS orders,
SUM(total) AS revenue,
AVG(total) AS aov
FROM orders
WHERE status = 'paid'
GROUP BY 1
ORDER BY 1;4. AOV по категориям
SELECT
category,
COUNT(*) AS orders,
AVG(total) AS aov,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY total) AS median_aov
FROM orders
WHERE status = 'paid'
GROUP BY category
ORDER BY aov DESC;5. AOV по каналу привлечения
SELECT
u.attribution_channel,
COUNT(*) AS orders,
AVG(o.total) AS aov
FROM orders o
JOIN users u ON u.user_id = o.user_id
WHERE o.status = 'paid'
GROUP BY u.attribution_channel
ORDER BY aov DESC;6. AOV новых vs повторных покупателей
WITH user_orders AS (
SELECT
user_id,
total,
created_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS order_num
FROM orders
WHERE status = 'paid'
)
SELECT
CASE WHEN order_num = 1 THEN 'new' ELSE 'returning' END AS buyer_type,
COUNT(*) AS orders,
AVG(total) AS aov
FROM user_orders
GROUP BY 1;Обычно AOV повторных > AOV новых (лояльные покупают больше).
7. AOV по сегменту пользователя (по ценности)
WITH user_ltv AS (
SELECT user_id, SUM(total) AS ltv
FROM orders WHERE status = 'paid'
GROUP BY user_id
),
user_tiers AS (
SELECT
user_id,
ltv,
NTILE(10) OVER (ORDER BY ltv DESC) AS decile
FROM user_ltv
)
SELECT
ut.decile,
COUNT(o.order_id) AS orders,
AVG(o.total) AS aov
FROM orders o
JOIN user_tiers ut ON ut.user_id = o.user_id
WHERE o.status = 'paid'
GROUP BY ut.decile
ORDER BY ut.decile;Дециль 1 (топ-10%) обычно имеет AOV в 2-3 раза выше среднего.
8. AOV по когортам
Как AOV меняется с «возрастом» пользователя:
WITH cohorts AS (
SELECT user_id, DATE_TRUNC('month', MIN(created_at)) AS cohort_month
FROM orders WHERE status = 'paid'
GROUP BY user_id
)
SELECT
c.cohort_month,
DATE_TRUNC('month', o.created_at) AS order_month,
COUNT(*) AS orders,
AVG(o.total) AS aov
FROM cohorts c
JOIN orders o ON o.user_id = c.user_id
WHERE o.status = 'paid'
GROUP BY c.cohort_month, DATE_TRUNC('month', o.created_at)
ORDER BY c.cohort_month, order_month;9. AOV с учётом скидок
Иногда важен AOV без скидок:
SELECT
AVG(total) AS aov_net, -- после скидки
AVG(total + discount) AS aov_gross, -- до скидки
AVG(discount) AS avg_discount
FROM orders
WHERE status = 'paid';10. AOV динамика (MoM)
WITH monthly AS (
SELECT
DATE_TRUNC('month', created_at) AS month,
AVG(total) AS aov
FROM orders
WHERE status = 'paid'
GROUP BY 1
)
SELECT
month,
aov,
LAG(aov) OVER (ORDER BY month) AS prev_month_aov,
(aov - LAG(aov) OVER (ORDER BY month)) / LAG(aov) OVER (ORDER BY month) * 100 AS mom_change_pct
FROM monthly
ORDER BY month;11. AOV + число заказов на покупателя = выручка на покупателя
WITH buyer_stats AS (
SELECT
user_id,
COUNT(*) AS orders_per_buyer,
AVG(total) AS aov_per_buyer,
SUM(total) AS rev_per_buyer
FROM orders WHERE status = 'paid'
GROUP BY user_id
)
SELECT
AVG(orders_per_buyer) AS avg_orders_per_buyer,
AVG(aov_per_buyer) AS avg_aov,
AVG(rev_per_buyer) AS avg_revenue_per_buyer
FROM buyer_stats;12. AOV с confidence interval
WITH stats AS (
SELECT
AVG(total) AS mean,
STDDEV(total) AS std,
COUNT(*) AS n
FROM orders WHERE status = 'paid'
)
SELECT
mean AS aov,
mean - 1.96 * std / SQRT(n) AS ci_lower,
mean + 1.96 * std / SQRT(n) AS ci_upper
FROM stats;Примеры с собеседований
AOV на собесе проверяют на понимании, что это среднее с хвостом.
«Как разложить выручку через AOV?» Сильный ответ: Выручка = трафик × конверсия × AOV, либо на уровне базы — AOV × частота покупок × число покупателей. Такая декомпозиция показывает, каким рычагом двигать выручку.
«AOV вырос — это всегда хорошо?» Слабый ответ — «да». Сильный: не обязательно. Если ушла аудитория с мелкими чеками, AOV вырастет, а выручка упадёт. Средний чек всегда читают вместе с числом заказов и общей выручкой.
«Почему к AOV смотрят медиану?» Сильный ответ: AOV — это среднее, оно чувствительно к выбросам. Несколько крупных заказов задирают средний чек, хотя типичный клиент платит меньше, поэтому распределение описывают медианой и перцентилями. Прогнать такие вопросы можно в Карьернике.
Частые ошибки
Ошибка 1. Включать отменённые заказы
-- завышает AOV, если есть cancelled с высоким total
AVG(total) FROM orders
-- правильно
AVG(total) FROM orders WHERE status = 'paid'Ошибка 2. AOV vs выручка на пользователя
AOV = выручка / число заказов. Выручка на пользователя = выручка / число пользователей. Это разные вещи, их часто путают.
Ошибка 3. Смотреть только AOV без медианы
Медиана важна. Если среднее — 2000 ₽, а медиана — 800 ₽, распределение сильно скошено.
Ошибка 4. Не нормализовать валюту
Если заказы в разных валютах — обязательно приведите к одной.
Ошибка 5. Смешивать разные категории
Одежда 5000 ₽ vs продукты 300 ₽. Общий AOV ничего не скажет. Сегментация критична.
Связанные темы
- Как посчитать средний чек в SQL
- Медиана vs среднее
- Percentile в SQL — шпаргалка
- Кейс: средний чек упал
- Потренироваться на SQL-тренажёре
FAQ
AOV или ARPU — что важнее?
Это разные метрики, и сравнивать их «в лоб» некорректно. AOV считается на один заказ, а ARPU — на пользователя за период. Смотреть стоит обе: AOV говорит о размере чека, ARPU — о ценности пользователя.
Какой нормальный AOV?
Универсальной нормы нет — всё зависит от категории. В продуктах питания это обычно 500–1500 ₽, в одежде — 2000–5000 ₽, в электронике — от 10 000 ₽ и выше.
AOV или медиана — что смотреть?
Смотреть нужно обе. Среднее удобно для бизнес-метрик и сравнения с прошлыми периодами, а медиана показывает типичного клиента — особенно на данных с тяжёлым хвостом, где несколько крупных заказов задирают среднее.
Как понять, AOV — средневзвешенный?
Да, AOV — это среднее сразу по всем заказам, поэтому крупные чеки весят в нём больше. Если нужен показатель «на пользователя», сначала посчитайте средний чек по каждому пользователю, а потом усредните уже эти значения.