Как посчитать повторные покупки в SQL

Проверь себя · 1/3разбор после ответа
Нужно посчитать число пользователей, которые сделали хотя бы 1 заказ (таблицы users(user_id) и orders(user_id, order_id)). Какой запрос посчитает правильно?

Зачем считать повторные покупки

Привлечение нового покупателя почти всегда стоит дороже удержания существующего — поэтому доля повторных покупок (repeat purchase rate) напрямую влияет на юнит-экономику. Продукты с сильным retention живут в основном на органике и возвратах, продукты со слабым — постоянно вынуждены перезакупаться в paid-каналах.

На собеседованиях в e-commerce эту метрику спрашивают регулярно: «посчитай долю повторных покупателей», «найди среднее время между заказами», «построй когортный repeat rate». Задачи решаются на оконных функциях — ROW_NUMBER, LAG, MIN / MAX.

Есть важные нюансы: считать долю повторных от всех или только от оплаченных заказов; считать ли два заказа за 5 минут как «один с разделённым платежом»; брать ли всех пользователей или ограничивать когортой, у которой уже был шанс вернуться.

В статье — готовые запросы:

  • базовый repeat purchase rate;
  • распределение по количеству заказов;
  • доля клиентов с 2+, 3+, 5+ заказами;
  • среднее время между покупками и время до второй;
  • repeat rate по когортам;
  • frequency (заказов на клиента в месяц);
  • revenue от повторных vs новых.

Схема: orders(order_id, user_id, total, status, created_at).

1. Базовый repeat purchase rate

WITH buyer_orders AS (
    SELECT
        user_id,
        COUNT(*) AS orders_count
    FROM orders
    WHERE status = 'paid'
    GROUP BY user_id
)
SELECT
    COUNT(*) AS total_buyers,
    COUNT(*) FILTER (WHERE orders_count >= 2) AS repeat_buyers,
    100.0 * COUNT(*) FILTER (WHERE orders_count >= 2) / COUNT(*) AS repeat_rate_pct
FROM buyer_orders;

2. Распределение по количеству заказов

WITH buyer_orders AS (
    SELECT user_id, COUNT(*) AS orders_count
    FROM orders WHERE status = 'paid'
    GROUP BY user_id
)
SELECT
    orders_count,
    COUNT(*) AS buyers,
    100.0 * COUNT(*) / SUM(COUNT(*)) OVER () AS pct_of_buyers
FROM buyer_orders
GROUP BY orders_count
ORDER BY orders_count;

Обычно: 60% делают 1 покупку, 20% — 2, 10% — 3, 5% — 4, 5% — 5+.

3. Доля клиентов с 2+, 3+, 5+

WITH buyer_orders AS (
    SELECT user_id, COUNT(*) AS cnt
    FROM orders WHERE status = 'paid'
    GROUP BY user_id
),
total AS (SELECT COUNT(*) AS n FROM buyer_orders)
SELECT
    100.0 * COUNT(*) FILTER (WHERE cnt >= 1) / n AS pct_1_plus,
    100.0 * COUNT(*) FILTER (WHERE cnt >= 2) / n AS pct_2_plus,
    100.0 * COUNT(*) FILTER (WHERE cnt >= 3) / n AS pct_3_plus,
    100.0 * COUNT(*) FILTER (WHERE cnt >= 5) / n AS pct_5_plus,
    100.0 * COUNT(*) FILTER (WHERE cnt >= 10) / n AS pct_10_plus
FROM buyer_orders, total;

4. Время между покупками

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 = 'paid'
)
SELECT
    user_id,
    AVG(EXTRACT(EPOCH FROM (created_at - prev_order)) / 86400) AS avg_days_between,
    MIN(EXTRACT(EPOCH FROM (created_at - prev_order)) / 86400) AS min_days,
    MAX(EXTRACT(EPOCH FROM (created_at - prev_order)) / 86400) AS max_days
FROM ordered
WHERE prev_order IS NOT NULL
GROUP BY user_id;

5. Среднее время до второй покупки

Отдельный KPI — «time to second purchase». Ключевой индикатор retention.

WITH user_orders AS (
    SELECT
        user_id,
        created_at,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS order_num
    FROM orders
    WHERE status = 'paid'
)
SELECT
    AVG(EXTRACT(EPOCH FROM (o2.created_at - o1.created_at)) / 86400) AS avg_days_to_2nd,
    PERCENTILE_CONT(0.5) WITHIN GROUP (
        ORDER BY EXTRACT(EPOCH FROM (o2.created_at - o1.created_at)) / 86400
    ) AS median_days_to_2nd
FROM user_orders o1
JOIN user_orders o2 ON o2.user_id = o1.user_id AND o2.order_num = 2
WHERE o1.order_num = 1;
Закрепи формулу povtornye pokupki в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать povtornye pokupki в Telegram

6. Repeat rate по когортам

Смотрим, улучшается ли retention новых когорт.

WITH cohorts AS (
    SELECT
        user_id,
        DATE_TRUNC('month', MIN(created_at)) AS cohort
    FROM orders
    WHERE status = 'paid'
    GROUP BY user_id
),
user_stats AS (
    SELECT user_id, COUNT(*) AS orders_count
    FROM orders WHERE status = 'paid'
    GROUP BY user_id
)
SELECT
    c.cohort,
    COUNT(*) AS cohort_size,
    COUNT(*) FILTER (WHERE us.orders_count >= 2) AS repeat_buyers,
    100.0 * COUNT(*) FILTER (WHERE us.orders_count >= 2) / COUNT(*) AS repeat_rate
FROM cohorts c
JOIN user_stats us USING (user_id)
GROUP BY c.cohort
ORDER BY c.cohort;

7. Frequency — заказов на клиента в месяц

WITH user_activity AS (
    SELECT
        user_id,
        COUNT(*) AS total_orders,
        -- общее число месяцев между первой и последней покупкой
        (EXTRACT(YEAR  FROM AGE(MAX(created_at), MIN(created_at))) * 12
         + EXTRACT(MONTH FROM AGE(MAX(created_at), MIN(created_at))))::NUMERIC + 1
            AS active_months
    FROM orders
    WHERE status = 'paid'
    GROUP BY user_id
    HAVING COUNT(*) >= 2   -- только пользователи с двумя и более заказами
)
SELECT
    AVG(total_orders / NULLIF(active_months, 0))         AS avg_frequency_per_month,
    PERCENTILE_CONT(0.5) WITHIN GROUP (
        ORDER BY total_orders / NULLIF(active_months, 0)
    )                                                    AS median_frequency
FROM user_activity;

В Postgres EXTRACT(MONTH FROM AGE(...)) даёт только компонент месяцев (0–11), поэтому прибавляем EXTRACT(YEAR FROM AGE(...)) * 12 — иначе данные за полтора года сожмутся в «6 месяцев».

8. Revenue от повторных vs новых

WITH user_order_num AS (
    SELECT
        user_id,
        total,
        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 'first_purchase' ELSE 'repeat' END AS type,
    COUNT(*) AS orders,
    SUM(total) AS revenue,
    AVG(total) AS avg_order_value
FROM user_order_num
GROUP BY 1;

Обычно repeat покупатели имеют выше AOV.

Частые ошибки

1. Включать тестовые аккаунты

Очищайте от сотрудников, QA-аккаунтов и ботов — иначе repeat rate завышен.

2. Не различать «двойные» заказы

Два заказа за 5 минут — часто один заказ с разделением оплаты. Такие случаи лучше схлопывать в одну транзакцию.

3. «Все клиенты» без когорт

Свежие пользователи просто не успели вернуться, и их включение занижает метрику. В отчёте по repeat rate разбивайте когорты по месяцу первой покупки.

4. Repeat rate без окна

Repeat rate за всё время ≠ за 90 дней. Чётко определяйте горизонт — например, «купил ещё раз в течение 90 дней после первой покупки».

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

FAQ

Что считается «повторным»?

2+ заказа. Иногда 3+ для более строгой метрики.

Repeat rate 30% — хорошо?

Очень зависит от категории. Для товаров разовой покупки 30% — неплохо, для food delivery с высокой частотой заказов — скорее тревожный сигнал. Сравнивайте со своими историческими значениями и с бенчмарками внутри ниши.

Как связано с LTV?

LTV ≈ AOV × frequency × lifespan. Repeat rate прямо влияет на lifespan.

Time to 2nd purchase — что это даёт?

Прокси для product-market fit. Короткое время → клиенты ценят.