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

Формула конверсии

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

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

Или в SQL:

SELECT
    COUNT(DISTINCT user_id) FILTER (WHERE action = 'purchase') * 100.0 /
    COUNT(DISTINCT user_id) AS conversion_pct
FROM events;

Вариант 1: через FILTER (PostgreSQL)

SELECT
    COUNT(*) FILTER (WHERE event = 'purchase') * 100.0 /
    COUNT(*) FILTER (WHERE event = 'landing') AS conversion
FROM events;

Чище читается.

Вариант 2: через CASE WHEN

Универсально для всех СУБД:

SELECT
    SUM(CASE WHEN event = 'purchase' THEN 1 ELSE 0 END) * 100.0 /
    SUM(CASE WHEN event = 'landing' THEN 1 ELSE 0 END) AS conversion
FROM events;

Воронка: конверсия по шагам

Для воронки view → cart → purchase:

WITH user_events AS (
    SELECT user_id,
        MAX(CASE WHEN event = 'view' THEN 1 ELSE 0 END) AS viewed,
        MAX(CASE WHEN event = 'cart' THEN 1 ELSE 0 END) AS carted,
        MAX(CASE WHEN event = 'purchase' THEN 1 ELSE 0 END) AS bought
    FROM events
    GROUP BY user_id
)
SELECT
    SUM(viewed) AS step1,
    SUM(carted) AS step2,
    SUM(bought) AS step3,
    SUM(carted) * 100.0 / NULLIF(SUM(viewed), 0) AS v_to_c,
    SUM(bought) * 100.0 / NULLIF(SUM(carted), 0) AS c_to_p,
    SUM(bought) * 100.0 / NULLIF(SUM(viewed), 0) AS overall
FROM user_events;

Важно: NULLIF защищает от деления на 0.

Если хочется сразу закрепить тему на практике — открой тренажёр в Telegram. 10 минут в день — и синтаксис в пальцах.

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

SELECT
    event_time::DATE AS day,
    COUNT(DISTINCT user_id) FILTER (WHERE event = 'purchase') * 100.0 /
    COUNT(DISTINCT user_id) FILTER (WHERE event = 'landing') AS conv_pct
FROM events
GROUP BY event_time::DATE
ORDER BY day;

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

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

Важные нюансы

1. По пользователям или по событиям

-- По пользователям (правильно)
COUNT(DISTINCT user_id) FILTER (WHERE event = 'purchase')

-- По событиям (часто завышает)
COUNT(*) FILTER (WHERE event = 'purchase')

Один пользователь может купить несколько раз — если считать по событиям, конверсия может превышать 100%.

2. Временное окно

Без окна — конверсия включает покупки через годы после первого визита. Часто нужно:

-- За сессию
WHERE purchase_time - first_visit_time < INTERVAL '1 hour'

-- За сутки
WHERE purchase_time::DATE = visit_time::DATE

3. Последовательность событий

«Купил после view». Используем LAG или JOIN по времени.

WITH seq AS (
    SELECT user_id, event,
        LAG(event) OVER (PARTITION BY user_id ORDER BY event_time) AS prev
    FROM events
)
SELECT COUNT(*) FILTER (WHERE event = 'purchase' AND prev = 'view') AS sequential
FROM seq;

Защита от деления на 0

Всегда:

numerator * 100.0 / NULLIF(denominator, 0)

NULLIF превращает 0 в NULL, деление на NULL даёт NULL — без ошибки.

Чтобы не только читать теорию, но и решать реальные задачи — загляните в бот Карьерника. Там по каждой теме подборка вопросов с разборами.

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

1. * 100 / COUNT(*) — integer division

-- ❌ Возвращает 0
COUNT(*) * 100 / COUNT(*)

-- ✅
COUNT(*) * 100.0 / COUNT(*)

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

Без DISTINCT пользователи с 10 заказами считаются 10 раз.

3. Забывать временное окно

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

4. Не учитывать сегменты

Средняя конверсия 3% может быть: iOS 5%, Android 1%. Сегментация — всегда.

Примеры задач

Задача 1. Конверсия регистрации → первой покупки

SELECT
    COUNT(DISTINCT o.user_id) * 100.0 / COUNT(DISTINCT u.user_id) AS conv
FROM users u
LEFT JOIN orders o ON o.user_id = u.user_id
WHERE u.registered_at >= '2026-04-01';

Задача 2. Конверсия в сегментах

SELECT segment,
    COUNT(DISTINCT user_id) FILTER (WHERE purchased) * 100.0 /
    COUNT(DISTINCT user_id) AS conv
FROM user_stats
GROUP BY segment;

Задача 3. Конверсия A/B-групп

SELECT ab_group,
    COUNT(DISTINCT user_id) FILTER (WHERE purchased) * 100.0 /
    COUNT(DISTINCT user_id) AS conv
FROM ab_test_users
GROUP BY ab_group;

Задача 4. Конверсия по месяцам

SELECT DATE_TRUNC('month', visit_time) AS month,
    COUNT(DISTINCT user_id) FILTER (WHERE purchased) * 100.0 /
    COUNT(DISTINCT user_id) AS conv
FROM sessions
GROUP BY 1 ORDER BY 1;

Читайте также

FAQ

Считать CTR или конверсию?

CTR = клики / показы. Конверсия = покупки / посещения. Разные метрики, разные воронки.

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

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

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

Зависит от определения: «конверсия в первую покупку» или «совершил покупку хоть раз». Уточняйте с PM.

Когда конверсия не информативна?

Когда нет сравнения: «конверсия 3%» — это хорошо или плохо? Нужен бенчмарк или тренд.