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

Проверь себя · 1/3разбор после ответа
Как получить начало текущей недели (недельный бакет) по текущей дате в PostgreSQL?

Зачем аналитику уметь считать 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 новых (лояльные покупают больше).

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

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 ничего не скажет. Сегментация критична.

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

FAQ

AOV или ARPU — что важнее?

Это разные метрики, и сравнивать их «в лоб» некорректно. AOV считается на один заказ, а ARPU — на пользователя за период. Смотреть стоит обе: AOV говорит о размере чека, ARPU — о ценности пользователя.

Какой нормальный AOV?

Универсальной нормы нет — всё зависит от категории. В продуктах питания это обычно 500–1500 ₽, в одежде — 2000–5000 ₽, в электронике — от 10 000 ₽ и выше.

AOV или медиана — что смотреть?

Смотреть нужно обе. Среднее удобно для бизнес-метрик и сравнения с прошлыми периодами, а медиана показывает типичного клиента — особенно на данных с тяжёлым хвостом, где несколько крупных заказов задирают среднее.

Как понять, AOV — средневзвешенный?

Да, AOV — это среднее сразу по всем заказам, поэтому крупные чеки весят в нём больше. Если нужен показатель «на пользователя», сначала посчитайте средний чек по каждому пользователю, а потом усредните уже эти значения.