Как посчитать CTR в SQL

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Зачем аналитику уметь считать CTR в SQL

CTR (Click-Through Rate) — базовая метрика любого performance-маркетинга и продуктовых коммуникаций. Она входит в формулу CAC: CAC = CPM / (CTR × CR). Если CTR проседает — CAC растёт, бюджет сгорает, маркетинг команда паникует.

На практике аналитика просят посчитать CTR не один раз, а десятки: по каналам, кампаниям, креативам, сегментам аудитории, дням. BI-отчёт в Google Ads или Я.Метрике часто не даёт нужной нарезки — проще написать SQL и получить именно тот разрез, который нужен.

В статье — готовые запросы для разных сценариев:

  • Общий CTR за период
  • По каналам и кампаниям
  • Динамика WoW с изменением
  • Blended (взвешенный) CTR по кампаниям
  • Сравнение A/B вариантов
  • Доверительный интервал

Типичная схема данных — таблица ad_events(user_id, event_type, channel, campaign, event_at) или агрегированная ad_stats(channel, campaign, day, impressions, clicks).

Формула

CTR = Clicks / Impressions × 100%

1. Общий CTR

SELECT
    COUNT(*) FILTER (WHERE event_type = 'click')::FLOAT /
    COUNT(*) FILTER (WHERE event_type = 'impression') AS ctr
FROM ad_events;

Или через CASE:

SELECT
    100.0 * SUM(CASE WHEN event_type = 'click' THEN 1 ELSE 0 END) /
    NULLIF(SUM(CASE WHEN event_type = 'impression' THEN 1 ELSE 0 END), 0) AS ctr_pct
FROM ad_events;

2. CTR по каналам

SELECT
    channel,
    SUM(impressions) AS imps,
    SUM(clicks) AS clicks,
    100.0 * SUM(clicks) / NULLIF(SUM(impressions), 0) AS ctr_pct
FROM ad_stats
GROUP BY channel
ORDER BY ctr_pct DESC;

3. CTR по дням

SELECT
    DATE(event_at) AS day,
    100.0 * SUM(clicks) / NULLIF(SUM(impressions), 0) AS ctr_pct
FROM ad_stats
GROUP BY 1
ORDER BY 1;

4. WoW change

WITH weekly AS (
    SELECT
        DATE_TRUNC('week', event_at) AS week,
        100.0 * SUM(clicks) / NULLIF(SUM(impressions), 0) AS ctr
    FROM ad_stats
    GROUP BY 1
)
SELECT
    week,
    ctr,
    LAG(ctr) OVER (ORDER BY week) AS prev_ctr,
    ctr - LAG(ctr) OVER (ORDER BY week) AS wow_diff
FROM weekly;

5. CTR по кампаниям с traffic share

SELECT
    campaign,
    SUM(impressions) AS imps,
    SUM(clicks) AS clicks,
    100.0 * SUM(clicks) / NULLIF(SUM(impressions), 0) AS ctr,
    100.0 * SUM(impressions) / SUM(SUM(impressions)) OVER () AS traffic_share
FROM ad_stats
GROUP BY campaign;

6. Blended CTR (weighted)

Если CTR считать как простое среднее по кампаниям — неверно. Нужно weighted:

-- неверно (simple mean)
SELECT AVG(ctr) FROM campaigns;

-- верно (weighted by impressions)
SELECT SUM(clicks) / SUM(impressions) AS blended_ctr
FROM campaigns;

7. CTR с доверительным интервалом

SELECT
    channel,
    clicks,
    impressions,
    ctr,
    ctr - 1.96 * SQRT(ctr * (1 - ctr) / impressions) AS ci_lower,
    ctr + 1.96 * SQRT(ctr * (1 - ctr) / impressions) AS ci_upper
FROM (
    SELECT
        channel,
        SUM(clicks) AS clicks,
        SUM(impressions) AS impressions,
        SUM(clicks)::FLOAT / SUM(impressions) AS ctr
    FROM ad_stats
    GROUP BY channel
) t;

8. Сравнение A/B

SELECT
    variant,
    SUM(clicks) AS clicks,
    SUM(impressions) AS impressions,
    100.0 * SUM(clicks) / SUM(impressions) AS ctr_pct
FROM experiment
GROUP BY variant;

Типичные CTR benchmarks

  • Search ads: 3-5%
  • Display: 0.1-0.5%
  • Social: 1-2%
  • Email: 2-5%
  • Push: 1-7%

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

Деление без NULLIF

/ 0 → ошибка.

Integer division

100 * 5 / 200 → 2 (integer). Используйте 100.0 * 5 / 200 → 2.5.

Average of averages

Простое среднее CTR по campaigns ≠ blended CTR. Нужно weighted.

Bot clicks

Bot filter важен перед подсчётом.

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

FAQ

CTR на impressions или на delivered?

Обычно impressions (подсчёт показанных). Для email — delivered.

Blended CTR правильно?

Weighted average по impressions. Simple mean — ошибочно.


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.