SQL для e-commerce аналитика
dt, platform, dau. Нужно вывести значение dau за предыдущий день для той же платформы, чтобы посчитать дневное изменение. Какое выражение верное?Зачем это знать
E-commerce — один из крупнейших нанимателей аналитиков в РФ. Wildberries, Ozon, Lamoda, Avito, Яндекс Маркет — все держат большие команды аналитиков и заваливают кандидатов SQL-задачами. Специфика в том, что вопросы не абстрактные, а завязаны на доменные метрики: GMV, средний чек, брошенные корзины, повторные покупки, возвраты.
Поэтому на собесе в e-commerce мало просто знать оконные функции и джойны — надо понимать, что этими джойнами считаешь и зачем бизнесу этот показатель. Ниже — типичные таблицы и готовые запросы на самые частые метрики, которые просят написать вживую.
Ключевые таблицы
Почти в любом e-commerce схема сводится к нескольким таблицам: заказы, позиции заказов, товары, пользователи и сессии. На собесе вам обычно дают примерно такую модель и просят по ней написать запрос.
-- orders: шапка заказа
(id, user_id, created_at, status, shipping_cost, discount)
-- order_items: позиции внутри заказа
(id, order_id, product_id, quantity, price)
-- products: каталог
(id, name, category, brand, cost)
-- users: покупатели
(id, signup_at, email)
-- sessions: визиты (для конверсии и атрибуции)
(id, user_id, started_at, device, utm_source)GMV (Gross Merchandise Value)
GMV — суммарная стоимость проданного товара за период. Это верхнеуровневая метрика оборота: сколько денег «прошло» через площадку до вычета возвратов и комиссий. Считается как сумма количество × цена по всем позициям завершённых заказов.
SELECT SUM(quantity * price) AS gmv
FROM order_items oi
JOIN orders o ON oi.order_id = o.id
WHERE o.status = 'completed'
AND o.created_at BETWEEN '2026-04-01' AND '2026-04-30';По дням — чтобы построить тренд и заметить провалы:
SELECT
DATE(o.created_at) AS day,
SUM(oi.quantity * oi.price) AS gmv
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
WHERE o.status = 'completed'
GROUP BY 1
ORDER BY 1;AOV (Average Order Value)
AOV — средний чек, то есть средняя сумма одного заказа. Важная тонкость: усреднять надо по заказам, а не по строкам позиций, поэтому сначала считаем сумму каждого заказа в подзапросе, а потом берём среднее.
SELECT
AVG(order_total) AS aov
FROM (
SELECT order_id, SUM(quantity * price) AS order_total
FROM order_items
GROUP BY order_id
) sub;По сегментам (например, по устройству) — чтобы увидеть, где чек выше:
SELECT
device,
AVG(order_total) AS aov,
COUNT(DISTINCT order_id) AS orders
FROM (
SELECT o.id AS order_id, o.device,
SUM(oi.quantity * oi.price) AS order_total
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, o.device
) s
GROUP BY device;Брошенные корзины (cart abandonment)
Доля корзин, которые создали, но не довели до покупки. Метрика ловит потери в самом конце воронки: человек добавил товар, но не оплатил. Считаем через флаги событий на уровне сессии.
WITH cart_events AS (
SELECT user_id, session_id,
MAX(CASE WHEN event = 'cart_add' THEN 1 ELSE 0 END) AS added_to_cart,
MAX(CASE WHEN event = 'checkout_start' THEN 1 ELSE 0 END) AS started_checkout,
MAX(CASE WHEN event = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM events
WHERE created_at BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY 1, 2
)
SELECT
SUM(added_to_cart) AS carts_created,
SUM(CASE WHEN added_to_cart = 1 AND purchased = 0 THEN 1 ELSE 0 END) AS abandoned,
SUM(CASE WHEN added_to_cart = 1 AND purchased = 0 THEN 1 ELSE 0 END) * 100.0 /
NULLIF(SUM(added_to_cart), 0) AS abandonment_rate
FROM cart_events;Доля повторных покупок (repeat purchase rate)
Показывает, какая доля покупателей вернулась за вторым заказом. Прокси для удержания: чем выше, тем меньше вы зависите от постоянной закупки нового трафика.
WITH user_orders AS (
SELECT user_id, COUNT(*) AS order_count
FROM orders
WHERE status = 'completed'
GROUP BY user_id
)
SELECT
COUNT(*) AS total_buyers,
COUNT(CASE WHEN order_count >= 2 THEN 1 END) AS repeat_buyers,
COUNT(CASE WHEN order_count >= 2 THEN 1 END) * 100.0 / COUNT(*) AS repeat_rate
FROM user_orders;Интервал между покупками
Среднее число дней между соседними заказами одного пользователя. Помогает понять частоту покупок и когда клиент «выпадает» из нормального цикла. Берём предыдущую дату заказа через LAG и считаем разницу.
WITH ordered AS (
SELECT
user_id,
created_at,
LAG(created_at) OVER (PARTITION BY user_id ORDER BY created_at) AS prev_order
FROM orders
WHERE status = 'completed'
)
SELECT
AVG(EXTRACT(DAY FROM (created_at - prev_order))) AS avg_days_between
FROM ordered
WHERE prev_order IS NOT NULL;Конверсия (conversion rate)
Доля сессий, которые закончились покупкой. Джойним сессии к заказам в узком временном окне, чтобы связать визит с оплатой, и считаем долю сконвертировавшихся.
WITH session_outcomes AS (
SELECT
s.id AS session_id,
s.user_id,
MAX(CASE WHEN o.id IS NOT NULL THEN 1 ELSE 0 END) AS converted
FROM sessions s
LEFT JOIN orders o ON o.user_id = s.user_id
AND o.created_at BETWEEN s.started_at AND s.started_at + INTERVAL '1 hour'
WHERE s.started_at >= CURRENT_DATE - 30
GROUP BY 1, 2
)
SELECT
AVG(converted) * 100 AS session_cr
FROM session_outcomes;Метрики по товарам
Топ продаж
SELECT
p.name,
p.category,
SUM(oi.quantity) AS units_sold,
SUM(oi.quantity * oi.price) AS revenue
FROM products p
JOIN order_items oi ON oi.product_id = p.id
JOIN orders o ON o.id = oi.order_id
WHERE o.status = 'completed'
AND o.created_at BETWEEN '2026-04-01' AND '2026-04-30'
GROUP BY p.id, p.name, p.category
ORDER BY revenue DESC
LIMIT 20;Доля возвратов по товару
Возвраты — больная тема e-commerce: высокий возврат съедает маржу и логистику. Считаем долю возвращённых заказов по каждому товару, отсекая товары с малой выборкой через HAVING.
SELECT
p.id, p.name,
COUNT(DISTINCT CASE WHEN o.status = 'completed' THEN o.id END) AS orders,
COUNT(DISTINCT CASE WHEN o.status = 'returned' THEN o.id END) AS returns,
COUNT(DISTINCT CASE WHEN o.status = 'returned' THEN o.id END) * 100.0 /
NULLIF(COUNT(DISTINCT o.id), 0) AS return_rate
FROM products p
JOIN order_items oi ON oi.product_id = p.id
JOIN orders o ON o.id = oi.order_id
GROUP BY p.id, p.name
HAVING COUNT(DISTINCT o.id) > 50
ORDER BY return_rate DESC;Маржа по товару
SELECT
p.name,
AVG(oi.price) AS avg_selling_price,
p.cost,
AVG(oi.price) - p.cost AS margin_per_unit,
(AVG(oi.price) - p.cost) / AVG(oi.price) * 100 AS margin_pct
FROM products p
JOIN order_items oi ON oi.product_id = p.id
GROUP BY p.id, p.name, p.cost;LTV клиента
LTV (lifetime value) — сколько денег в среднем приносит один покупатель за всё время. Базовая версия — суммарная выручка на пользователя и среднее число заказов. В реальности LTV ещё учитывает маржу и горизонт, но на собесе часто хватает такой оценки.
WITH user_ltv AS (
SELECT
user_id,
MIN(created_at) AS first_order,
COUNT(*) AS total_orders,
SUM(order_total) AS total_spent
FROM orders_with_totals
GROUP BY user_id
)
SELECT
AVG(total_spent) AS ltv,
AVG(total_orders) AS avg_orders_per_user
FROM user_ltv;Первая покупка против повторной
Сравнение экономики первого и последующих заказов. Часто первый чек ниже (скидка новичку), а повторные — прибыльнее. Нумеруем заказы пользователя через ROW_NUMBER и делим на две группы.
WITH order_rank AS (
SELECT
id, user_id, order_total,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS order_num
FROM orders
WHERE status = 'completed'
)
SELECT
CASE WHEN order_num = 1 THEN 'First-time' ELSE 'Repeat' END AS customer_type,
COUNT(*) AS orders,
AVG(order_total) AS aov,
SUM(order_total) AS revenue
FROM order_rank
GROUP BY 1;Анализ по категориям
SELECT
p.category,
SUM(oi.quantity * oi.price) AS revenue,
COUNT(DISTINCT o.id) AS orders,
COUNT(DISTINCT o.user_id) AS buyers,
AVG(oi.quantity * oi.price) AS avg_line_value
FROM products p
JOIN order_items oi ON oi.product_id = p.id
JOIN orders o ON o.id = oi.order_id
WHERE o.status = 'completed'
GROUP BY p.category
ORDER BY revenue DESC;Типичные дашборды
Аналитик e-commerce почти всегда отвечает за набор регулярных дашбордов. Понимать, что на них выводят, полезно и для работы, и для собеса:
- GMV, выручка и число заказов — верхнеуровневая динамика.
- Воронка конверсии: визит → корзина → чекаут → покупка.
- Тренд среднего чека (AOV).
- Топ товаров и категорий по выручке.
- Сегменты покупателей: новые против вернувшихся.
- Атрибуция по каналам привлечения.
- Доля возвратов.
- Оборачиваемость запасов (продвинутый уровень).
Как это спрашивают на собесе
Помимо «напиши запрос на метрику X», в e-commerce любят продуктовые вопросы, где SQL — только часть ответа.
«Как бы вы измеряли успех e-commerce?» North Star обычно GMV или контрибуционная маржа. Дальше раскладываете на драйверы: трафик, конверсия, средний чек, доля повторных покупок. Сильный ответ — не назвать одну метрику, а показать дерево: как верхняя метрика собирается из нижних.
«Продажи упали — как будете разбираться?» Это задача на root cause analysis. Идёте по декомпозиции: где именно упало — в новых или вернувшихся клиентах? В конкретных категориях? На отдельных каналах или устройствах? Сломалась ли оплата? SQL здесь — инструмент, чтобы последовательно сузить гипотезу до конкретного сегмента.
Интервьюер смотрит не только на синтаксис, но и на то, уточняете ли вы определения (какой статус считать продажей, включать ли возвраты) и думаете ли о бизнес-смысле метрики.
Связанные темы
FAQ
Чем Gross GMV отличается от Net GMV?
Gross GMV — оборот до вычета возвратов и отмен, Net GMV — после. Разница бывает большой в категориях с высоким возвратом (одежда, обувь). На собесе всегда уточняйте, какое определение имеет в виду интервьюер, прежде чем писать запрос.
AOV или ARPU — что использовать?
AOV считается на один заказ, ARPU — на одного пользователя за период. Они связаны через частоту покупок: ARPU ≈ AOV × число заказов на пользователя. Для оценки чека берут AOV, для оценки ценности клиента — ARPU или LTV.
Почему AOV нельзя считать простым AVG по order_items?
Потому что одна строка order_items — это одна позиция, а не заказ. Прямой AVG(quantity * price) усреднит по позициям и занизит чек. Сначала надо свернуть позиции в сумму заказа через GROUP BY order_id, и только потом усреднять.
Чем юнит-экономика e-commerce сложнее, чем в SaaS?
В физическом e-commerce на маржу влияют доставка, себестоимость товара, возвраты и хранение — всё это надо вычитать из выручки. В SaaS предельная себестоимость почти нулевая, поэтому юнит-экономика проще. На собесе в маркетплейсе ждут, что вы упомянете возвраты и логистику как часть contribution margin.
Как считать конверсию — по сессиям или по пользователям?
Зависит от вопроса. Session CR (доля сессий с покупкой) показывает эффективность конкретных визитов и полезна для оценки UX и трафика. User CR (доля пользователей, купивших за период) ближе к бизнес-результату. На собесе стоит проговорить, какую именно конверсию считаете и почему.
Тренируйте SQL для аналитики — откройте тренажёр с 1500+ вопросами для собесов.