Как посчитать Repeat Buyer Rate в SQL
DISTINCT в SELECT-запросе?Содержание:
Зачем Repeat Buyer Rate
Repeat Buyer — это покупатель, который совершил две и более покупки. Repeat Buyer Rate (доля таких покупателей) — базовая метрика e-commerce: она показывает, возвращаются ли люди за повторной покупкой, то есть насколько продукт реально нужен, а не куплен один раз под акцию. Привлечение нового клиента дорого стоит, поэтому повторные покупки — это, по сути, «бесплатная» выручка с уже оплаченного трафика, и рост этой метрики напрямую улучшает unit-экономику.
Формула
Repeat Buyer Rate = покупатели_с_2+_заказами / всего_покупателей × 100%Окно наблюдения чаще всего берут 12 месяцев или lifetime (за всё время). Выбор окна критичен: одна и та же база даст разные цифры на 90 днях и за всю жизнь, поэтому окно фиксируют в определении метрики.
Базовый расчёт
Считаем число оплаченных заказов на покупателя и смотрим долю тех, у кого их два и больше:
WITH order_counts AS (
SELECT
user_id,
COUNT(*) AS order_count
FROM orders
WHERE status = 'paid'
AND created_at >= CURRENT_DATE - INTERVAL '12 months'
GROUP BY user_id
)
SELECT
COUNT(*) AS total_customers,
COUNT(*) FILTER (WHERE order_count >= 2) AS repeat_buyers,
COUNT(*) FILTER (WHERE order_count >= 2)::NUMERIC * 100 / COUNT(*) AS repeat_buyer_rate_pct
FROM order_counts;Фильтр status = 'paid' важен: в знаменателе только реально оплатившие клиенты, отменённые и неоплаченные заказы в расчёт не идут. FILTER (WHERE order_count >= 2) отбирает повторных покупателей.
По когортам
Средний rate по всей базе обманчив: свежие покупатели физически не успели купить второй раз. Честнее считать долю повторных покупок по когорте месяца первой покупки:
WITH first_purchase AS (
SELECT
user_id,
MIN(created_at) AS first_order_at
FROM orders
WHERE status = 'paid'
GROUP BY user_id
),
cohort_repeat AS (
SELECT
DATE_TRUNC('month', fp.first_order_at) AS cohort,
fp.user_id,
COUNT(o.order_id) AS total_orders
FROM first_purchase fp
LEFT JOIN orders o ON o.user_id = fp.user_id
AND o.status = 'paid'
AND o.created_at <= fp.first_order_at + INTERVAL '12 months'
WHERE fp.first_order_at >= CURRENT_DATE - INTERVAL '18 months'
AND fp.first_order_at < CURRENT_DATE - INTERVAL '6 months'
GROUP BY 1, fp.user_id
)
SELECT
cohort,
COUNT(*) AS customers,
COUNT(*) FILTER (WHERE total_orders >= 2) AS repeat_buyers,
COUNT(*) FILTER (WHERE total_orders >= 2)::NUMERIC * 100 / COUNT(*) AS repeat_rate_pct
FROM cohort_repeat
GROUP BY cohort
ORDER BY cohort;Обратите внимание на два условия. Окно + INTERVAL '12 months' даёт каждой когорте одинаковый срок на повторную покупку. А фильтр по first_order_at < CURRENT_DATE - INTERVAL '6 months' отсекает слишком свежие когорты, у которых окно ещё не закрылось, — иначе сравнение было бы нечестным.
Время до второй покупки
Не менее важно, как быстро происходит вторая покупка — это подсказывает, когда включать реактивацию:
WITH ordered AS (
SELECT
user_id,
created_at AS order_at,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS rn
FROM orders
WHERE status = 'paid'
),
first_two AS (
SELECT
user_id,
MAX(order_at) FILTER (WHERE rn = 1) AS order1,
MAX(order_at) FILTER (WHERE rn = 2) AS order2
FROM ordered
WHERE rn <= 2
GROUP BY user_id
)
SELECT
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (order2 - order1)) / 86400) AS median_days,
AVG(EXTRACT(EPOCH FROM (order2 - order1)) / 86400) AS avg_days
FROM first_two
WHERE order2 IS NOT NULL;Считаем и медиану, и среднее: распределение времени до второй покупки обычно скошено (часть клиентов возвращается сразу, часть — спустя месяцы), поэтому медиана надёжнее среднего.
По каналам привлечения
Разбивка по каналу показывает, откуда приходят лояльные покупатели, а не разовые «охотники за скидкой»:
SELECT
u.acquisition_channel,
COUNT(DISTINCT u.user_id) AS customers,
COUNT(DISTINCT u.user_id) FILTER (
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.user_id
AND o.status = 'paid'
GROUP BY o.user_id
HAVING COUNT(*) >= 2
)
) AS repeat_buyers,
COUNT(DISTINCT u.user_id) FILTER (
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.user_id
AND o.status = 'paid'
GROUP BY o.user_id
HAVING COUNT(*) >= 2
)
)::NUMERIC * 100 / COUNT(DISTINCT u.user_id) AS repeat_rate
FROM users u
WHERE u.acquired_at >= CURRENT_DATE - INTERVAL '12 months'
GROUP BY u.acquisition_channel
ORDER BY repeat_rate DESC;Такой разрез часто важнее среднего числа: канал с дешёвым привлечением, но нулевым повтором проигрывает более дорогому каналу с высоким repeat rate — по LTV, а не по цене клика.
Как это спрашивают на собесе
Метрику любят на собесах в e-commerce и retail — она проверяет и SQL, и понимание, что повтор ≠ retention.
«Чем repeat buyer rate отличается от retention?» Retention обычно про удержание по периодам (вернулся ли пользователь в месяц N), а repeat buyer rate — про сам факт повторной покупки за окно. Это близкие, но разные метрики, и путать их на собесе — минус.
«Почему свежая когорта показывает низкий repeat rate?» Потому что новым покупателям физически не хватило времени на вторую покупку. Поэтому метрику считают по когортам с равным окном и отрезают когорты, у которых окно ещё не закрылось. Разобрать такие продуктовые кейсы с решением можно в Карьернике.
«Как выбрать окно — 90 дней, 12 месяцев, lifetime?» Зависит от цикла покупки: для товаров с частым потреблением подойдёт короткое окно, для дорогих редких покупок — длинное. Главное — зафиксировать окно и не сравнивать метрики, посчитанные на разных окнах.
«Считать ли возвраты?» Заказ, который потом вернули, обычно исключают: это не удержанный клиент, а несостоявшаяся покупка. Иначе метрика завышается.
Частые ошибки
- Свежие покупатели занижают метрику. Когорта, привлечённая месяц назад, показывает низкий repeat rate просто потому, что не успела купить второй раз. Считайте по когортам с равным окном.
- Не учитывать возвраты. Заказ вернули — клиента нельзя считать повторным покупателем. Возвраты надо исключать, иначе метрика завышается.
- Смешивать разные окна. 12 месяцев и lifetime дают разные цифры. Сравнивать их между собой нельзя — фиксируйте одно окно для всех расчётов.
- Размытое определение «повтора». 2+ заказа или 3+? Второй заказ в течение 30 дней или когда угодно? От определения зависит цифра, поэтому его надо зафиксировать явно.
- Дубли из-за нескольких аккаунтов. Один человек с двумя аккаунтами выглядит как два разных «разовых» покупателя. Без склейки идентичности (identity resolution) метрика занижается.
Связанные темы
- Как посчитать days between orders в SQL
- Как посчитать repeat purchase rate в SQL
- Как посчитать order frequency в SQL
- Как посчитать LTV в SQL
FAQ
Какой Repeat Buyer Rate считается нормальным?
Ориентиры сильно зависят от категории: для среднего e-commerce это примерно 20–30%, для подписок и товаров регулярного потребления — 40–60%, а лучшие в классе доходят до 70%+. Абсолютную цифру всегда сравнивают внутри своей категории, а не между разными типами бизнеса.
Repeat buyer и returning customer — это одно и то же?
Часто их используют как синонимы, но акцент разный: «repeat buyer» — про факт второй покупки после первой, «returning customer» — про возврат после периода неактивности. В конкретной компании стоит уточнить, что именно вкладывают в термин, чтобы считать одно и то же.
Какое окно выбрать — 30, 90 дней или lifetime?
Зависит от цикла покупки: для частого потребления берут короткое окно, для редких дорогих покупок — длинное или lifetime. 90 дней — распространённый компромисс. Главное — выбрать одно окно и последовательно им пользоваться, иначе метрики нельзя сравнивать.
Как повысить Repeat Buyer Rate?
Рабочие рычаги: email-реактивация и напоминания, программы лояльности, перевод на подписку для расходуемых товаров и снижение трения при повторной покупке (сохранённые карты, быстрый повторный заказ). Все они бьют в одно — упростить и мотивировать второй заказ.
Насколько повторные покупатели ценнее первичных?
Заметно ценнее: их LTV обычно в разы выше, чем у разовых покупателей, а стоимость удержания ниже стоимости привлечения нового клиента. Поэтому маркетинговый бюджет на удержание этого сегмента часто окупается лучше, чем расходы на привлечение.