Как посчитать AOV в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Что такое AOV
AOV (Average Order Value) — средний чек заказа.
AOV = Total revenue / Total ordersОдна из ключевых метрик в e-commerce, маркетплейсах, сервисах доставки.
Схема данных
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;Обычно repeat AOV > new AOV (лояльные покупают больше).
7. AOV по сегменту пользователя (value tier)
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 (top 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 + orders per buyer = revenue per buyer
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;Частые ошибки
Ошибка 1. Включать отменённые заказы
-- завышает AOV, если есть cancelled с высоким total
AVG(total) FROM orders
-- правильно
AVG(total) FROM orders WHERE status = 'paid'Ошибка 2. AOV vs Revenue per User
AOV = revenue / orders. Revenue per user = revenue / users. Разные вещи, часто путают.
Ошибка 3. Ср. AOV и median — только среднее
Медиана важна. Если AVG 2000 ₽, median 800 ₽ — распределение сильно скошено.
Ошибка 4. Не нормализовать валюту
Если заказы в разных валютах — обязательно приведите к одной.
Ошибка 5. Смешивать разные категории
Одежда 5000 ₽ vs продукты 300 ₽. Общий AOV ничего не скажет. Сегментация критична.
Связанные темы
- Как посчитать средний чек в SQL
- Медиана vs среднее
- Percentile в SQL — шпаргалка
- Кейс: средний чек упал
FAQ
AOV или ARPU — что важнее?
Разные метрики. AOV — на заказ. ARPU — на пользователя. Обе важны.
Какой нормальный AOV?
Зависит от категории. Продукты: 500-1500 ₽. Одежда: 2000-5000 ₽. Электроника: 10 000+ ₽.
AOV или medium — что смотреть?
Обе. Среднее — для бизнес-метрик. Медиана — для понимания типичного клиента.
Как понять, AOV — средневзвешенный?
Да, он среднее по всем заказам. Если хотите «по пользователю» — сначала AVG(total) per user, потом среднее по users.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.