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

Проверь себя · 1/3разбор после ответа
В таблице events(created_at) нужно выбрать события за последние 7 дней с точностью до текущего момента (включая сегодняшние, ещё не завершённые сутки). Какое условие в WHERE корректнее?

Формула

Базовая формула простая:

Конверсия = пользователи, выполнившие действие
           ----------------------------------- × 100%
            все пользователи

На практике важно держать в голове три вещи:

  • считать именно уникальных пользователей, а не события;
  • ограничивать период окном, в котором конверсия имеет смысл (сессия, сутки, 7 дней);
  • заботиться о дроблении по сегментам, иначе среднее будет врать.

Конверсия в разных отраслях

Что считать конверсией и какой у неё ориентир, зависит от продукта.

E-commerce. Сквозная конверсия visit→purchase обычно 1–3%, но её всегда читают по шагам воронки (view→cart→checkout→pay). Главный рычаг — шаг оплаты: даже небольшое улучшение checkout заметно поднимает выручку.

SaaS. Работает воронка lead→trial→paid, и trial→paid определяется активацией внутри триала. Низкая конверсия в оплату почти всегда упирается не в цену, а в то, что пользователь не дошёл до ценности за пробный период.

Перформанс-реклама. Конверсию считают от клика до целевого действия и связывают с CPA. Высокий CTR при низкой конверсии — сигнал нерелевантного оффера: креатив цепляет, но страница не выполняет обещание.

1. Конверсия за период

SELECT
    COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'purchase')
        * 100.0
        / NULLIF(COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'landing'), 0)
    AS conversion_pct
FROM events
WHERE event_at >= '2026-04-01'
  AND event_at <  '2026-05-01';

В FILTER фильтруются только те строки, которые попадают в знаменатель и числитель. COUNT(DISTINCT user_id) исключает повторные события одного пользователя, NULLIF(..., 0) защищает от падения запроса, если пользователей на лендинге не оказалось.

2. Универсальный вариант через CASE WHEN

FILTER есть не во всех СУБД. Эквивалент на CASE WHEN работает везде:

SELECT
    COUNT(DISTINCT CASE WHEN event_name = 'purchase' THEN user_id END)
        * 100.0
        / NULLIF(
            COUNT(DISTINCT CASE WHEN event_name = 'landing' THEN user_id END),
            0
          ) AS conversion_pct
FROM events;

3. Воронка view → cart → purchase

WITH user_events AS (
    SELECT
        user_id,
        MAX(CASE WHEN event_name = 'view'     THEN 1 ELSE 0 END) AS viewed,
        MAX(CASE WHEN event_name = 'cart'     THEN 1 ELSE 0 END) AS carted,
        MAX(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) AS bought
    FROM events
    GROUP BY user_id
)
SELECT
    SUM(viewed) AS step1_view,
    SUM(carted) AS step2_cart,
    SUM(bought) AS step3_purchase,
    SUM(carted) * 100.0 / NULLIF(SUM(viewed), 0) AS cr_view_to_cart,
    SUM(bought) * 100.0 / NULLIF(SUM(carted), 0) AS cr_cart_to_purchase,
    SUM(bought) * 100.0 / NULLIF(SUM(viewed), 0) AS cr_overall
FROM user_events;

Такая «простая» воронка игнорирует порядок событий — если важно учитывать, что cart случился после view, используйте запрос из статьи про воронку (как посчитать воронку в SQL).

4. Конверсия по дням

SELECT
    event_at::DATE AS day,
    COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'purchase')
        * 100.0
        / NULLIF(COUNT(DISTINCT user_id) FILTER (WHERE event_name = 'landing'), 0)
    AS conversion_pct
FROM events
GROUP BY 1
ORDER BY 1;

5. Конверсия по каналам

SELECT
    u.channel,
    COUNT(DISTINCT e.user_id) FILTER (WHERE e.event_name = 'purchase') AS buyers,
    COUNT(DISTINCT e.user_id)                                           AS visitors,
    COUNT(DISTINCT e.user_id) FILTER (WHERE e.event_name = 'purchase')
        * 100.0
        / NULLIF(COUNT(DISTINCT e.user_id), 0) AS conversion_pct
FROM users u
JOIN events e USING (user_id)
GROUP BY u.channel
ORDER BY conversion_pct DESC;

Важно: «посетители» здесь — все пользователи канала, у которых есть хоть какой-то event. Для paid-каналов имеет смысл ограничить ещё и типом события (например, «landing»).

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

6. Конверсия с временным окном

Покупка должна случиться не когда угодно, а за разумный срок после первого визита:

WITH first_visit AS (
    SELECT
        user_id,
        MIN(event_at) AS first_visit_at
    FROM events
    WHERE event_name = 'landing'
    GROUP BY user_id
),
purchases AS (
    SELECT DISTINCT user_id
    FROM events e
    JOIN first_visit fv USING (user_id)
    WHERE e.event_name = 'purchase'
      AND e.event_at <= fv.first_visit_at + INTERVAL '7 days'
)
SELECT
    COUNT(DISTINCT p.user_id) * 100.0
    / NULLIF(COUNT(DISTINCT fv.user_id), 0) AS cr_7d
FROM first_visit fv
LEFT JOIN purchases p USING (user_id);

Окно — ключевая деталь. «Конверсия за весь год» обычно бессмысленна, если цикл покупки в среднем занимает несколько дней.

7. Конверсия A/B-групп

SELECT
    ab_group,
    COUNT(*)                                                AS users,
    COUNT(*) FILTER (WHERE purchased)                       AS buyers,
    100.0 * COUNT(*) FILTER (WHERE purchased) / COUNT(*)    AS conversion_pct
FROM ab_test_users
GROUP BY ab_group;

Чтобы не делать выводов на глаз, сравнение удобно расширить до классического A/B-анализа.

8. Конверсия регистрации → первая покупка

SELECT
    DATE_TRUNC('month', u.registered_at)::DATE AS cohort,
    COUNT(DISTINCT u.user_id)                   AS signups,
    COUNT(DISTINCT o.user_id)                   AS first_buyers,
    100.0 * COUNT(DISTINCT o.user_id) / NULLIF(COUNT(DISTINCT u.user_id), 0) AS conversion_pct
FROM users u
LEFT JOIN orders o
       ON o.user_id  = u.user_id
      AND o.status   = 'paid'
GROUP BY 1
ORDER BY 1;

Такой запрос удобно положить в дашборд: сразу видно, как конвертится каждая новая месячная когорта.

Примеры с собеседований

Конверсию на собесе проверяют на понимании воронки и базы.

«Как связаны пошаговая и сквозная конверсия?» Сильный ответ: сквозная равна произведению пошаговых. Поэтому общий рост ищут через самый узкий шаг, а не через средний по воронке.

«Конверсия упала — как будешь разбираться?» Слабый ответ — «посмотрю почему». Сильный: найти, на каком шаге провал, проверить состав трафика (не пригнали ли низкокачественную аудиторию), разрез по платформе, каналу и гео, сравнение с сезонностью.

«На какую базу делить конверсию каждого шага?» Сильный ответ: на тех, кто реально дошёл до предыдущего шага, а не на всех подряд. Иначе шаги несопоставимы, и вывод о «слабом» шаге будет неверным. Прогнать такие вопросы можно в Карьернике.

Типичные ошибки

1. Целочисленное деление

-- 0: integer / integer
COUNT(*) * 100 / COUNT(*)

-- OK: 100.0 гарантирует дробную арифметику
COUNT(*) * 100.0 / COUNT(*)

2. События вместо пользователей

COUNT(*) считает события, а не пользователей. Один покупатель с пятью заказами даст «+5» в числителе — конверсия может превысить 100%. Для корректного расчёта используйте COUNT(DISTINCT user_id).

3. Отсутствие временного окна

«Конверсия за весь год» без окна почти всегда выглядит выше реальной. Ограничьте период, в котором событие «покупка» имеет смысл после «визита».

4. Сегменты

Средняя конверсия 3% может быть собрана из iOS с 5% и Android с 1%. Средняя по больнице редко бывает полезной — всегда показывайте сегментированную картину хотя бы по платформе и стране.

5. Нет защиты от деления на ноль

Если в знаменателе может оказаться 0, запрос упадёт. Всегда оборачивайте в NULLIF(x, 0).

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

FAQ

Конверсия или CTR — это одно и то же?

Нет. CTR — клики / показы, метрика верхней части воронки. Конверсия — доля пользователей, дошедших до целевого действия (покупки, регистрации, активации).

Макро или микро конверсия?

Макро — конечное действие (покупка, первая подписка). Микро — промежуточные (клик, просмотр товара, добавление в корзину). В отчётах полезно показывать оба уровня.

Как обрабатывать повторные покупки?

Зависит от определения метрики. «Конверсия в первую покупку» измеряется по MIN(created_at) по каждому пользователю. «Совершил покупку хоть раз за период» — по наличию любого события purchase в окне. Согласуйте определение с PM перед тем, как строить дашборд.

Когда конверсия перестаёт быть информативной?

Когда её не с чем сравнить. «CR 3%» сам по себе ничего не говорит — нужен либо тренд, либо бенчмарк, либо сравнение с предыдущим периодом.