Все материалы
Продуктовая аналитикапрактикумсредний

Анализ рекламных каналов в SQL: от регистрации до выручки

Как оценить рекламный канал в SQL: воронка от регистрации до оплаты, выручка на пользователя, честное окно сравнения, интервалы и порог цены привлечения, когда расходов нет.

КейсПрактика2 октября 2026 г.17 мин

Анализ рекламного канала в SQL идёт по одному пути: регистрации, активация, первая оплата, выручка на человека — и на каждом шаге канал сравнивают с другими на одинаковом окне. Окупаемость так не посчитать: для неё нужны расходы, а без них запрос отвечает только, при какой цене привлечения выручка их вернёт. Ниже этот путь пройден для платного поиска (paid_search) учебной базы «Маяк»: менеджер спрашивает, оставлять ли каналу бюджет, и в конце получает записку на шесть строк.

Какие вопросы задают про рекламный канал?

Маркетинг пишет: платный поиск за два месяца привёл 1 015 регистраций, давайте добавим бюджет. Менеджер продукта пересылает сообщение аналитику с одним вопросом: оставлять ли каналу деньги. Число регистраций на него не отвечает — оно говорит только, что канал умеет приводить людей.

Вопрос о бюджете раскладывается на пять более узких. У каждого своя метрика, свой знаменатель и своё окно — срок, за который у пришедших было время сделать действие.

База — подписочный сервис «Маяк» из симулятора SQL-аналитика: 4 613 регистраций с 1 июня по 29 августа 2026 года, события в продукте и оплаты; выгрузка закрыта 30 августа в 13:00. Каналов четыре: органика (organic), рекомендации (referral), партнёры (partner) и платный поиск. С 1 июля, когда его запустили, он даёт 28,4% регистраций.

Как этот вопрос выглядит среди других задач недели, показано в статье о работе продуктового аналитика.

Пять вопросов о канале и чем на них отвечают
ВопросМетрикаЗнаменатель и окноПлатный поиск
Сколько людей привёл?регистрациивсе регистрации канала1 015 с 1 июля
Доходят ли до первой пользы?доля создавших рабочее пространстворегистрации с 1 июля по 29 августа37,04%
Платят ли?доля сделавших первую оплатурегистрации с 1 июля по 9 августа10,79%
Сколько денег на человека?выручка за 30 дней на зарегистрированногорегистрации июля2,59 доллара
Окупается ли?стоимость привлечения (CAC), возврат инвестиций (ROMI), срок окупаемостирасходы каналарасходов в базе нет

Какое окно взять, чтобы сравнение каналов было честным?

Первый запрос к любому каналу — когда он появился. Платный поиск стартовал 1 июля, остальные три канала работают с 1 июня. Если считать по всей базе, у старых каналов окажется лишний месяц: июньские регистрации успели и оплатить, и продлить подписку.

Вторая граница — зрелость. Зрелая регистрация — та, у которой срок, отведённый на действие, целиком попал в выгрузку. Рабочее пространство в этой базе создают в день регистрации, поэтому для активации годятся все регистрации по 29 августа (30 августа — неполный день, события обрываются в 12:59). Первая оплата здесь приходит на 3–20-й день: срок истёк у всех, кто пришёл не позже 9 августа. Выручке за 30 дней нужны полные 30 дней жизни — это регистрации июля. В рабочих данных срок берут из распределения: за сколько дней после регистрации приходит, например, 95% первых оплат.

Без окна платный поиск выглядит слабее, чем есть: по всем регистрациям первую оплату сделали 86 из 1 015, 8,47%. На зрелом окне — 67 из 621, 10,79%. Разницу дают 394 человека, пришедшие после 9 августа: срок оплаты у них ещё идёт.

В записке окно стоит рядом с числом, иначе маркетинг посчитает по всей базе и получит другой ответ.

Шкала времени с 1 июня по 30 августа: остальные каналы работают с 1 июня, окно активации платного поиска — 1 июля – 29 августа, окно первой оплаты — 1 июля – 9 августа, окно выручки за 30 дней — регистрации июля.
Три окна платного поиска. Тёмная часть полосы — когда пришли пользователи, светлая — срок, который у них был на действие.
Когда запущен канал и сколько регистраций попадает в каждое окно
SELECT channel,
       MIN(signup_date) AS first_signup,
       COUNT(*) AS signups,
       SUM(CASE WHEN signup_date >= DATE '2026-07-01'
                THEN 1 ELSE 0 END) AS since_july,
       SUM(CASE WHEN signup_date >= DATE '2026-07-01'
                 AND signup_date < DATE '2026-08-10'
                THEN 1 ELSE 0 END) AS for_payment,
       SUM(CASE WHEN signup_date >= DATE '2026-07-01'
                 AND signup_date < DATE '2026-08-01'
                THEN 1 ELSE 0 END) AS for_revenue_30d
FROM users
GROUP BY channel
ORDER BY signups DESC;

-- organic     | 2026-06-01 | 1747 | 1269 | 780 | 597
-- referral    | 2026-06-01 | 1037 |  745 | 449 | 352
-- paid_search | 2026-07-01 | 1015 | 1015 | 621 | 482
-- partner     | 2026-06-01 |  814 |  542 | 315 | 245

Как собрать воронку канала: регистрация, активация, оплата?

Воронка канала здесь — три числа: сколько человек пришло, какая доля создала рабочее пространство (это активация, первое полезное действие) и какая доля сделала первую оплату. Считаем людей. У пользователя много событий и бывает несколько платежей, и прямое соединение трёх таблиц размножит строки. В запросе ниже каждое действие становится флагом 0 или 1 через EXISTS: на человека остаётся одна строка.

Платный поиск последний на обоих шагах. Пространство создали 37,04% пришедших; у партнёров 50,92%, у органики 65,80%, у рекомендаций 74,23%. Первую оплату на зрелом окне сделали 10,79% против 21,27%, 25,51% и 31,40%. По всей базе, с июньскими регистрациями, активация старых каналов чуть выше — 74,7%, 66,7% и 53,2%; так она посчитана в статье о воронке в SQL.

По объёму канал второй после органики: 1 015 регистраций с 1 июля против 1 269. Объём — аргумент маркетинга, доли — аргумент продукта; для решения нужны оба и ещё цена. Как выбирают само активирующее действие, разбирает статья про activation rate.

Активация и первая оплата по каналам, % зарегистрированных

Учебная база. Активация — регистрации с 1 июля по 29 августа, первая оплата — регистрации с 1 июля по 9 августа.

Создали рабочее пространствоСделали первую оплату
Воронка по каналам: активация на регистрациях с 1 июля, первая оплата — на зрелых
WITH flags AS (
  SELECT u.channel,
         CASE WHEN u.signup_date < DATE '2026-08-10'
              THEN 1 ELSE 0 END AS mature,
         CASE WHEN EXISTS (SELECT 1 FROM events e
                           WHERE e.user_id = u.user_id
                             AND e.event_name = 'workspace_created')
              THEN 1 ELSE 0 END AS activated,
         CASE WHEN EXISTS (SELECT 1 FROM payments p
                           WHERE p.user_id = u.user_id)
              THEN 1 ELSE 0 END AS paid
  FROM users u
  WHERE u.signup_date >= DATE '2026-07-01'
)
SELECT channel,
       COUNT(*) AS signups,
       SUM(activated) AS activated,
       ROUND(CAST(100.0 * SUM(activated) / COUNT(*)
                  AS numeric(18, 6)), 2) AS activated_pct,
       SUM(mature) AS mature_signups,
       SUM(mature * paid) AS payers,
       ROUND(CAST(100.0 * SUM(mature * paid) / SUM(mature)
                  AS numeric(18, 6)), 2) AS paid_pct
FROM flags
GROUP BY channel
ORDER BY paid_pct DESC;

-- referral    |  745 | 553 | 74.23 | 449 | 141 | 31.40
-- organic     | 1269 | 835 | 65.80 | 780 | 199 | 25.51
-- partner     |  542 | 276 | 50.92 | 315 |  67 | 21.27
-- paid_search | 1015 | 376 | 37.04 | 621 |  67 | 10.79

Можно ли читать активацию и оплату как шаги одной воронки?

Напрашивается вывод: активация низкая, поэтому и платят мало; починим первый экран — оплаты вырастут. Вывод держится на допущении, что оплата идёт после активации. Проверка простая: посчитать оплативших отдельно среди создавших пространство и среди не создавших.

Из 67 оплативших в платном поиске 34 пространства не создавали. Оплата в этой базе активации не требует, и «регистрация → активация → оплата» — два параллельных показателя, а не лестница.

Интервал разницы уходит от нуля в одном канале из четырёх. В платном поиске среди создавших пространство оплатили 14,54%, среди не создавших — 8,63%: разница 5,91 процентного пункта (п. п.); 95%-й доверительный интервал — диапазон значений, совместимых с данными, — от 0,55 до 11,27. В остальных каналах разница от −3,36 до +2,23 п. п., и каждый интервал накрывает ноль.

Один интервал из четырёх — слабое основание. Когда связи нет нигде, хотя бы один из четырёх 95%-х интервалов уходит от нуля случайно примерно в 19% случаев. С поправкой Бонферрони на четыре сравнения интервал платного поиска — от −0,92 до 12,74 — накрывает ноль (см. множественные сравнения).

В учебной базе ответ к тому же известен: генератор задаёт оплату независимо от активации, так что находка ложная. Это свойство учебных данных; в своём продукте связь проверяют тем же расчётом с поправкой на число срезов, а причинность — экспериментом. Фразу «низкая активация съедает оплаты» эти данные не поддерживают.

Доля оплативших среди создавших и не создавших рабочее пространство, регистрации с 1 июля по 9 августа
КаналСоздали пространствоНе создалиРазница, п. п. (95%-й интервал)
referral30,59% (104 из 340)33,94% (37 из 109)−3,36 (от −13,51 до 6,79)
organic25,78% (132 из 512)25,00% (67 из 268)0,78 (от −5,64 до 7,20)
partner22,36% (36 из 161)20,13% (31 из 154)2,23 (от −6,80 до 11,26)
paid_search14,54% (33 из 227)8,63% (34 из 394)5,91 (от 0,55 до 11,27)
Оплатившие среди создавших и не создавших рабочее пространство
WITH flags AS (
  SELECT u.channel,
         CASE WHEN EXISTS (SELECT 1 FROM events e
                           WHERE e.user_id = u.user_id
                             AND e.event_name = 'workspace_created')
              THEN 1 ELSE 0 END AS activated,
         CASE WHEN EXISTS (SELECT 1 FROM payments p
                           WHERE p.user_id = u.user_id)
              THEN 1 ELSE 0 END AS paid
  FROM users u
  WHERE u.signup_date >= DATE '2026-07-01'
    AND u.signup_date < DATE '2026-08-10'
)
SELECT channel,
       SUM(activated) AS activated,
       SUM(activated * paid) AS activated_paid,
       SUM(1 - activated) AS not_activated,
       SUM((1 - activated) * paid) AS not_activated_paid
FROM flags
GROUP BY channel
ORDER BY channel;

-- organic     | 512 | 132 | 268 | 67
-- paid_search | 227 |  33 | 394 | 34
-- partner     | 161 |  36 | 154 | 31
-- referral    | 340 | 104 | 109 | 37

Сколько денег приносит зарегистрированный и сколько — платящий?

Доля оплативших ещё не деньги: тарифы стоят 19, 29 и 39 долларов, а подписку продлевают. Денежная метрика канала — выручка на одного зарегистрированного за фиксированный срок жизни; её называют когортным ARPU или LTV за 30 дней. В знаменателе все пришедшие, включая тех, кто не заплатил.

По всей базе выходит 2,43 доллара на регистрацию у платного поиска против 8,99 у рекомендаций — разрыв в 3,7 раза. Сравнение нечестное: в сумме рекомендаций есть июньские регистрации с продлениями, у платного поиска июня не было.

На равном сроке — регистрации июля, первые 30 дней жизни каждого — платный поиск приносит 2,59 доллара на человека. У партнёров 4,78, у органики 6,70, у рекомендаций 7,83. Разрыв с лидером сократился до 3,0 раза, порядок каналов прежний.

Теперь та же выручка на платящего: 24,00 доллара у платного поиска и от 23,40 до 25,33 у остальных. За первые 30 дней у каждого платящего ровно одна оплата (продление приходит не раньше 33-го дня), так что это средняя цена выбранного тарифа, и между каналами она расходится меньше чем на 2 доллара. Почти вся разница в деньгах сидит в доле платящих.

Выручка здесь — оплаты из таблицы payments; возвратов и налогов в базе нет. ARPU за календарный месяц по активным пользователям — другая метрика, о ней статья про ARPU и ARPPU; как выручка когорты копится по дням жизни, показывает статья про LTV.

Выручка на зарегистрированного за 30 дней
доля платящих × выручка на платящего = 52 / 482 × 24,00 = 2,59 доллара

Платный поиск, регистрации июля. У рекомендаций то же произведение: 114 / 352 × 24,18 = 7,83.

Выручка за первые 30 дней на зарегистрированного и на платящего, регистрации июля
WITH first_30 AS (
  SELECT u.user_id, u.channel,
         COALESCE(SUM(p.amount), 0) AS revenue
  FROM users u
  LEFT JOIN payments p
    ON p.user_id = u.user_id
   AND p.paid_at < u.signup_date + 30
  WHERE u.signup_date >= DATE '2026-07-01'
    AND u.signup_date < DATE '2026-08-01'
  GROUP BY u.user_id, u.channel
)
SELECT channel,
       COUNT(*) AS signups,
       SUM(CASE WHEN revenue > 0 THEN 1 ELSE 0 END) AS payers,
       SUM(revenue) AS revenue,
       ROUND(CAST(SUM(revenue) / COUNT(*)
                  AS numeric(18, 6)), 2) AS per_signup,
       ROUND(CAST(SUM(revenue) / SUM(CASE WHEN revenue > 0 THEN 1 ELSE 0 END)
                  AS numeric(18, 6)), 2) AS per_payer,
       ROUND(CAST(STDDEV_SAMP(revenue) AS numeric(18, 6)), 3) AS sd
FROM first_30
GROUP BY channel
ORDER BY per_signup DESC;

-- referral    | 352 | 114 | 2756.00 | 7.83 | 24.18 | 11.947
-- organic     | 597 | 158 | 4002.00 | 6.70 | 25.33 | 11.730
-- partner     | 245 |  50 | 1170.00 | 4.78 | 23.40 |  9.839
-- paid_search | 482 |  52 | 1248.00 | 2.59 | 24.00 |  7.767

Как понять, что разница между каналами не случайна?

В платном поиске 621 зрелая регистрация и 67 оплативших, у партнёров — 315 и 67. Доли на таких группах гуляют на несколько пунктов сами по себе, поэтому рядом с разницей ставят 95%-й доверительный интервал.

Для разницы долей хватает формулы со стандартной ошибкой; для разницы средней выручки она та же, только с выборочным стандартным отклонением из последнего столбца запроса про деньги.

Ближайший к платному поиску канал — партнёры. По доле оплативших они выше на 10,48 п. п. (интервал от 5,34 до 15,62), по выручке за 30 дней — на 2,19 доллара (от 0,77 до 3,60). Оба интервала не доходят до нуля, а с органикой и рекомендациями разрыв ещё больше. Отставание платного поиска шумом не объяснить.

Интервал есть и у самих 2,59 доллара: от 1,90 до 3,28. Выручка на человека — величина с перекосом: у девяти из десяти ноль, у остальных 19–39 долларов. На 482 наблюдениях нормальное приближение ещё работает: бутстреп на 10 000 повторных выборок даёт почти те же границы — примерно 1,9 и 3,3.

Насколько каналы выше платного поиска: разница и 95%-й интервал
КаналДоля оплативших, п. п. (регистрации 1 июля – 9 августа)Выручка за 30 дней, доллары (регистрации июля)
partner10,48 (от 5,34 до 15,62)2,19 (от 0,77 до 3,60)
organic14,72 (от 10,81 до 18,64)4,11 (от 2,95 до 5,28)
referral20,61 (от 15,68 до 25,55)5,24 (от 3,81 до 6,67)
pythonpandas: интервалы для разницы долей и разницы средней выручки
import numpy as np
import pandas as pd

# строки двух запросов выше: зрелые регистрации и оплатившие;
# регистрации июля, выручка за 30 дней и её стандартное отклонение
df = pd.DataFrame(
    {'n_pay': [449, 780, 315, 621], 'x_pay': [141, 199, 67, 67],
     'n_rev': [352, 597, 245, 482], 'revenue': [2756, 4002, 1170, 1248],
     'sd': [11.947, 11.730, 9.839, 7.767]},
    index=['referral', 'organic', 'partner', 'paid_search'],
)
z = 1.96  # квантиль нормального распределения для 95%-го интервала
base = 'paid_search'

p = df.x_pay / df.n_pay
var_p = p * (1 - p) / df.n_pay
d_p = 100 * (p - p[base])
se_p = 100 * np.sqrt(var_p + var_p[base])

m = df.revenue / df.n_rev
var_m = df.sd ** 2 / df.n_rev
d_m = m - m[base]
se_m = np.sqrt(var_m + var_m[base])

out = pd.DataFrame({
    'paid_diff_pp': d_p, 'paid_low': d_p - z * se_p, 'paid_high': d_p + z * se_p,
    'rev_diff': d_m, 'rev_low': d_m - z * se_m, 'rev_high': d_m + z * se_m,
}).drop(base).round(2)
print(out.to_string())

half = z * np.sqrt(var_m[base])
print(f'{base}: {m[base]:.2f} [{m[base] - half:.2f}; {m[base] + half:.2f}]')

#           paid_diff_pp  paid_low  paid_high  rev_diff  rev_low  rev_high
# referral         20.61     15.68      25.55      5.24     3.81      6.67
# organic          14.72     10.81      18.64      4.11     2.95      5.28
# partner          10.48      5.34      15.62      2.19     0.77      3.60
# paid_search: 2.59 [1.90; 3.28]

Не объясняется ли отставание составом аудитории?

Прежде чем называть канал слабым, проверяют, не сидит ли разница в составе. У платного поиска он заметно другой: 65,5% зрелых регистраций пришли с мобильных, в остальных каналах — 42,2%. Если с телефона платят реже, часть отставания — свойство устройства.

Проверка — стандартизация: долю оплативших каждого канала пересчитывают так, будто устройства в нём распределены как во всём окне (51,1% десктопа). После пересчёта у платного поиска 11,14% вместо 10,79%, но сдвигаются и остальные каналы: у партнёров 21,67% вместо 21,27%. Разрыв с партнёрами остался прежним — 10,5 п. п., с органикой сократился с 14,7 до 14,2, с рекомендациями — с 20,6 до 20,1. Устройства объясняют не больше половины пункта разрыва. Внутри платного поиска десктоп и мобильные неразличимы: 12,15% и 10,07%, разница 2,08 п. п. при интервале от −3,19 до 7,34.

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

Первая оплата по каналам и устройствам на зрелом окне
SELECT u.channel, u.device,
       COUNT(*) AS signups,
       COUNT(p.user_id) AS payers,
       ROUND(CAST(100.0 * COUNT(p.user_id) / COUNT(*)
                  AS numeric(18, 6)), 2) AS paid_pct
FROM users u
LEFT JOIN payments p
  ON p.user_id = u.user_id
 AND p.payment_type = 'first'
WHERE u.signup_date >= DATE '2026-07-01'
  AND u.signup_date < DATE '2026-08-10'
GROUP BY u.channel, u.device
ORDER BY u.channel, u.device;

-- organic     | desktop | 432 | 119 | 27.55
-- organic     | mobile  | 348 |  80 | 22.99
-- paid_search | desktop | 214 |  26 | 12.15
-- paid_search | mobile  | 407 |  41 | 10.07
-- partner     | desktop | 196 |  39 | 19.90
-- partner     | mobile  | 119 |  28 | 23.53
-- referral    | desktop | 265 |  85 | 32.08
-- referral    | mobile  | 184 |  56 | 30.43
pythonpandas: доля оплативших при общем для всех каналов составе устройств
import pandas as pd

# строки запроса выше: канал, устройство, зрелые регистрации, оплатившие
df = pd.DataFrame(
    [('organic', 'desktop', 432, 119), ('organic', 'mobile', 348, 80),
     ('paid_search', 'desktop', 214, 26), ('paid_search', 'mobile', 407, 41),
     ('partner', 'desktop', 196, 39), ('partner', 'mobile', 119, 28),
     ('referral', 'desktop', 265, 85), ('referral', 'mobile', 184, 56)],
    columns=['channel', 'device', 'signups', 'payers'],
)

# общий состав устройств окна: веса, одинаковые для всех каналов
mix = df.groupby('device')['signups'].sum() / df['signups'].sum()

totals = df.groupby('channel')[['signups', 'payers']].sum()
mobile = df[df.device == 'mobile'].set_index('channel')['signups']
weighted = df.payers / df.signups * df.device.map(mix)

out = pd.DataFrame({
    'mobile_pct': 100 * mobile / totals.signups,
    'raw_pct': 100 * totals.payers / totals.signups,
    'standardized_pct': 100 * weighted.groupby(df.channel).sum(),
}).round(2)
print(out.to_string())

#              mobile_pct  raw_pct  standardized_pct
# channel
# organic           44.62    25.51             25.32
# paid_search       65.54    10.79             11.14
# partner           37.78    21.27             21.67
# referral          40.98    31.40             31.27

Не портится ли канал с ростом объёма?

Регистраций из платного поиска становится больше: 92 в первую полную неделю июля и 137 в последнюю полную неделю августа. Частый страх — с ростом объёма в канал попадает всё более случайная аудитория. Смотрим долю оплативших по неделям регистрации.

Если взять все недели, картина пугает: 11,97% у пришедших 3–9 августа, затем 9,40%, 4,38% и 1,43%. Это ловушка зрелости: у трёх последних недель срок оплаты ещё идёт. Таблица «по неделям» без отметки зрелости читается как отчёт о деградации канала.

На шести зрелых неделях доля колеблется от 5,08% до 13,49% без направления. Критерий хи-квадрат проверяет, совместим ли такой разброс с одной общей долей на все шесть недель; ожидаемых оплативших в каждой неделе больше шести, так что он применим. Совместим: p = 0,41 — при 59–126 регистрациях в неделе доли расходятся так же или сильнее в четырёх случаях из десяти и без всяких перемен в канале. Направление хи-квадрат не проверяет; критерий тренда по номеру недели (Кокрана — Армитиджа) даёт p = 0,66. Спада эти данные не показывают, но и исключить небольшой не могут: оплативших всего 67.

Доля сделавших первую оплату в платном поиске по неделям регистрации: шесть зрелых недель от 5,08 до 13,49 процента с широкими интервалами и три незрелые недели — 9,40, 4,38 и 1,43 процента.
Платный поиск: доля оплативших по неделям регистрации. Спад в конце — недели, у которых срок оплаты ещё не истёк. Усы — интервалы Уилсона: в первой неделе всего три оплаты.
Платный поиск по неделям регистрации: первая оплата и отметка зрелости
SELECT CAST(date_trunc('week', u.signup_date) AS DATE) AS week_start,
       COUNT(*) AS signups,
       COUNT(p.user_id) AS payers,
       ROUND(CAST(100.0 * COUNT(p.user_id) / COUNT(*)
                  AS numeric(18, 6)), 2) AS paid_pct,
       CASE WHEN MAX(u.signup_date) < DATE '2026-08-10'
            THEN 1 ELSE 0 END AS mature
FROM users u
LEFT JOIN payments p
  ON p.user_id = u.user_id
 AND p.payment_type = 'first'
WHERE u.channel = 'paid_search'
GROUP BY CAST(date_trunc('week', u.signup_date) AS DATE)
ORDER BY week_start;

-- 2026-06-29 |  59 |  3 |  5.08 | 1
-- 2026-07-06 |  92 | 10 | 10.87 | 1
-- 2026-07-13 | 126 | 17 | 13.49 | 1
-- 2026-07-20 | 117 | 15 | 12.82 | 1
-- 2026-07-27 | 110 |  8 |  7.27 | 1
-- 2026-08-03 | 117 | 14 | 11.97 | 1
-- 2026-08-10 | 117 | 11 |  9.40 | 0
-- 2026-08-17 | 137 |  6 |  4.38 | 0
-- 2026-08-24 | 140 |  2 |  1.43 | 0

Чего SQL не скажет без данных о расходах?

Всё посчитанное выше — отдача канала. Для решения о бюджете нужна вторая половина: сколько стоит регистрация. CAC, ROMI и срок окупаемости делят деньги на деньги, а расходов в учебной базе нет. Написать «канал убыточен» по этим данным нельзя, «канал окупается» — тоже.

Зато вопрос можно развернуть: при какой цене привлечения выручка вернёт расходы. Июльская регистрация принесла за 30 дней 2,59 доллара. Значит, расходы возвращаются выручкой первых 30 дней, если регистрация стоит не дороже 2,59 доллара, а платящий пользователь — не дороже 24,00. С учётом интервала порог лежит между 1,90 и 3,28. Это условный расчёт, оценкой окупаемости он не служит: о расходах в нём нет ни одного числа, и он допускает, что следующие регистрации поведут себя как июльские.

У порога четыре оговорки. Первая — горизонт. 30 дней выбраны по данным: дольше июльские регистрации не прожили. Какой срок окупаемости приемлем, решает бизнес. Вторая: порог посчитан по выручке, а расходы возвращает маржа; при условной марже 70% он опускается до 1,81 доллара.

Третья: порог видит одну оплату. Из 32 первых оплат платного поиска, сделанных по 31 июля, продлены 17 — 53,1% против 58,1% в остальных каналах; интервал разницы — от −12,9 до 22,8 п. п., на 32 наблюдениях доли неразличимы. Допустим, платящие из поиска продлевают как все. Тогда к 60-му дню порог вырастет в 1,6 раза, примерно до 4,1 доллара: во столько выросла выручка июньских регистраций. Это допущение можно проверить после 28 сентября, когда июльским регистрациям исполнится 60 дней.

Четвёртая оговорка — атрибуция. В таблице users у человека один канал, и база не говорит, по какому правилу он записан: по первому визиту или по последнему. Часть регистраций платного поиска могла прийти и без рекламы; сколько их, покажет эксперимент с отключением рекламы на части аудитории.

У маркетинга нужно запросить расходы по дням и кампаниям, правило атрибуции, маржу и допустимый срок окупаемости. Что делать с ними дальше — в статьях про CAC, ROI и ROMI, ДРР и срок окупаемости.

Условный расчёт для регистраций июля: какую долю расходов вернёт выручка первых 30 дней при разной цене регистрации
Цена регистрации, долларыРасходы на 482 регистрацииВыручка за 30 днейВозврат
1,004821 248259%
2,591 2481 248100%
3,001 4461 24886%
5,002 4101 24852%
Сколько первых оплат, сделанных по 31 июля, продлены через 30 дней
SELECT CASE WHEN u.channel = 'paid_search'
            THEN 'paid_search' ELSE 'other' END AS source,
       COUNT(*) AS first_payments,
       COUNT(r.user_id) AS renewed,
       ROUND(CAST(100.0 * COUNT(r.user_id) / COUNT(*)
                  AS numeric(18, 6)), 1) AS renewed_pct
FROM payments f
JOIN users u ON u.user_id = f.user_id
LEFT JOIN payments r
  ON r.user_id = f.user_id
 AND r.payment_type = 'renewal'
 AND r.paid_at = f.paid_at + 30
WHERE f.payment_type = 'first'
  AND f.paid_at <= DATE '2026-07-31'
GROUP BY CASE WHEN u.channel = 'paid_search'
              THEN 'paid_search' ELSE 'other' END
ORDER BY source;

-- other       | 470 | 273 | 58.1
-- paid_search |  32 |  17 | 53.1

Как написать записку менеджеру о канале?

Записка отвечает на вопрос менеджера в первой строке и помещается на один экран. Порядок: вопрос, ответ, числа с окном, что проверено, оговорки, следующий шаг. Запросы уходят в приложение.

Ответ записан как правило с порогами. Когда маркетинг пришлёт цену регистрации, пересчитывать ничего не придётся: достаточно сравнить её с двумя числами.

  • Вопрос: оставлять ли бюджет платного поиска и увеличивать ли его.
  • Ответ: решает цена регистрации, а расходов в базе нет. Дешевле 1,90 доллара — расходы возвращаются выручкой первых 30 дней, бюджет оставляем. Дороже 3,28 — за 30 дней не возвращаются: бюджет не наращиваем, пока не посчитана выручка за 60 дней. Между порогами данных для ответа мало.
  • Числа: из 621 зарегистрированного с 1 июля по 9 августа первую оплату сделали 10,79% (95%-й интервал от 8,35 до 13,23); у ближайшего канала, партнёров, 21,27%. Июльская регистрация принесла за 30 дней 2,59 доллара (от 1,90 до 3,28) против 4,78–7,83 в других каналах.
  • Что проверено: отставание не сводится к мобильному трафику (при общем составе устройств разрыв с партнёрами те же 10,5 п. п.), а между неделями регистрации различий не видно (p = 0,41).
  • Оговорки: пороги посчитаны по выручке, с условной маржой 70% они ниже — 1,33 и 2,30; 30 дней видят одну оплату, а продлений у канала пока 17 из 32; правило атрибуции неизвестно; расчёт переносит июльские регистрации на будущие.
  • Дальше: запросить расходы по дням и кампаниям, маржу и допустимый срок окупаемости; после 28 сентября пересчитать выручку за 60 дней; связь активации с оплатой проверить экспериментом на трафике платного поиска.

Частые вопросы об анализе рекламных каналов

Как посчитать эффективность каналов привлечения в SQL?

Сведите события и оплаты до одной строки на пользователя, сгруппируйте по каналу и посчитайте доли и выручку на зарегистрированного на общем окне. Это отдача канала; эффективность в деньгах появится, когда к ней присоединят расходы.

Как посчитать выручку по каналам в SQL и не задвоить оплаты?

Сначала сложите платежи по пользователю, затем присоедините результат к users через LEFT JOIN. Контроль — сумма выручки по каналам равна сумме таблицы payments: в этой базе 30 639 долларов.

Чем ARPU по каналам отличается от LTV?

ARPU делит выручку периода на пользователей периода, LTV копит выручку когорты за срок её жизни. Выручка на зарегистрированного за 30 дней — это LTV на горизонте 30 дней; с ARPU за календарный месяц её не сравнивают.

Можно ли посчитать CAC и ROMI в SQL?

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

Что делать, если у канала мало данных?

Расширять окно, пока оно остаётся зрелым, и показывать интервал. Если интервал накрывает и «окупается», и «не окупается», честный ответ — «данных недостаточно» и дата, когда их хватит.

Что повторить самому и что читать дальше?

Все запросы статьи выполняются в песочнице симулятора SQL-аналитика на этой же базе; тот же сюжет с собственной запиской — глава «Самостоятельный кейс: бюджет paid_search».

Упражнение: посчитайте выручку за 30 дней на зарегистрированного в платном поиске отдельно для десктопа и мобильных (регистрации июля). Должно получиться 2,33 доллара на 168 человек и 2,73 на 314; разница 0,40 при интервале от −0,96 до 1,76. Разницы порога между устройствами эти данные не показывают.

Удержание тех же каналов разобрано в статье про retention по когортам и каналам.

Продолжить чтение
Вся библиотека