Как посчитать средний чек в SQL
Формула среднего чека
Средний чек (AOV) = Общая выручка / Количество заказовВ SQL — проще некуда:
SELECT AVG(amount) AS aov FROM orders;Или эквивалентно:
SELECT SUM(amount) / COUNT(*) AS aov FROM orders;По периодам
SELECT DATE_TRUNC('month', created_at) AS month,
AVG(amount) AS aov,
COUNT(*) AS orders,
SUM(amount) AS revenue
FROM orders
GROUP BY 1 ORDER BY 1;Видим динамику AOV по месяцам.
По сегментам
По каналам
SELECT u.channel, AVG(o.amount) AS aov
FROM users u JOIN orders o USING (user_id)
GROUP BY u.channel
ORDER BY aov DESC;По платформам
SELECT platform, AVG(amount) AS aov
FROM orders
GROUP BY platform;По городам
SELECT u.city, AVG(o.amount) AS aov
FROM users u JOIN orders o USING (user_id)
GROUP BY u.city
HAVING COUNT(*) > 100 -- исключаем города с малым числом заказов
ORDER BY aov DESC;Средний чек клиента (не заказа)
Если нужен средний чек на пользователя за период:
WITH user_totals AS (
SELECT user_id, SUM(amount) AS total
FROM orders
WHERE created_at >= '2026-04-01'
GROUP BY user_id
)
SELECT AVG(total) AS avg_per_user FROM user_totals;Это revenue per paying user (ARPPU), не AOV.
Медиана vs среднее
Если распределение скошено (несколько китов с огромными чеками) — среднее врёт.
SELECT
AVG(amount) AS mean_aov,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY amount) AS median_aov,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY amount) AS p95_aov
FROM orders;Если mean > median — правый хвост. Подробнее про медиану.
Прокачать тему на реальных задачах удобно в боте @kariernik_bot — база вопросов собрана с собеседований в Яндексе, Авито, Ozon, Тинькофф.
AOV по категориям с долей
WITH cat_stats AS (
SELECT p.category,
AVG(o.amount) AS aov,
SUM(o.amount) AS rev,
COUNT(*) AS orders
FROM products p JOIN orders o USING (product_id)
GROUP BY p.category
)
SELECT category, aov, rev,
ROUND(100.0 * rev / SUM(rev) OVER (), 2) AS share_pct
FROM cat_stats
ORDER BY rev DESC;MoM изменение AOV
WITH monthly AS (
SELECT DATE_TRUNC('month', created_at) AS month,
AVG(amount) AS aov
FROM orders GROUP BY 1
)
SELECT month, aov,
LAG(aov) OVER (ORDER BY month) AS prev_aov,
ROUND(100.0 * (aov - LAG(aov) OVER (ORDER BY month)) /
LAG(aov) OVER (ORDER BY month), 2) AS mom_pct
FROM monthly;AOV с фильтрами
Только оплаченные заказы:
SELECT AVG(amount) AS paid_aov
FROM orders
WHERE status = 'paid';Только заказы от новых пользователей:
SELECT AVG(o.amount) AS new_user_aov
FROM orders o
JOIN users u USING (user_id)
WHERE u.registered_at >= o.created_at - INTERVAL '30 day';AOV: items per order × avg item price
Альтернативная декомпозиция:
WITH order_details AS (
SELECT o.order_id,
COUNT(*) AS items_count,
AVG(oi.price) AS avg_item_price,
SUM(oi.price * oi.quantity) AS order_total
FROM orders o JOIN order_items oi USING (order_id)
GROUP BY o.order_id
)
SELECT
AVG(items_count) AS avg_items_per_order,
AVG(avg_item_price) AS avg_item_price,
AVG(order_total) AS aov
FROM order_details;Если AOV упал — проверяем, что именно: количество товаров в корзине или средняя цена.
AOV по A/B-группам
SELECT ab_group, AVG(amount) AS aov, COUNT(*) AS orders
FROM orders o
JOIN ab_test_users USING (user_id)
WHERE o.created_at BETWEEN test_start AND test_end
GROUP BY ab_group;На собесе такие штуки часто спрашивают. Быстрый способ довести до автоматизма — тренажёр в Telegram с задачами из реальных интервью.
Частые ошибки
1. Считать AOV включая отменённые
-- ❌ Завышает AOV
SELECT AVG(amount) FROM orders;
-- ✅ Только paid
SELECT AVG(amount) FROM orders WHERE status = 'paid';2. Integer division
-- ❌
SELECT SUM(amount) / COUNT(*) FROM orders;
-- Может быть int division если amount = int
-- ✅
SELECT SUM(amount)::NUMERIC / COUNT(*) FROM orders;
-- Или просто AVG(amount) — всегда корректен3. Не учитывать возвраты
Если refund-ы вычитаются отдельной записью, AOV не отражает фактическую выручку.
4. Смешивать AOV и ARPPU
- AOV = revenue / orders.
- ARPPU = revenue / paying users.
У одного платящего может быть несколько заказов — значения разные.
Визуализация AOV
import seaborn as sns
import matplotlib.pyplot as plt
sns.lineplot(data=monthly_aov, x='month', y='aov')
plt.axhline(monthly_aov['aov'].mean(), color='red', linestyle='--', label='Avg')
plt.legend()Интерпретация
- AOV растёт — клиенты больше покупают, upsell работает.
- AOV падает, orders растут — привлекли дешёвый сегмент.
- AOV падает, orders падают — проблема с продуктом.
Читайте также
FAQ
AOV считать на заказ или на пользователя?
AOV — на заказ (Average Order Value). На пользователя — ARPPU.
Нужна ли медиана?
Для метрик с тяжёлыми хвостами (чек в e-commerce премиума) — да, показывайте оба. Для LTV — обязательно.
Как обрабатывать возвраты?
Вычитать из выручки: SUM(amount - COALESCE(refund, 0)). Или считать «net AOV».
AOV в разных валютах?
Конвертируйте в одну (обычно USD/RUB) через таблицу курсов. Не смешивайте валюты в среднем.