Анализ рекламных каналов в SQL: от регистрации до выручки
Как оценить рекламный канал в SQL: воронка от регистрации до оплаты, выручка на пользователя, честное окно сравнения, интервалы и порог цены привлечения, когда расходов нет.
Содержание статьи
Анализ рекламного канала в 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 августа: срок оплаты у них ещё идёт.
В записке окно стоит рядом с числом, иначе маркетинг посчитает по всей базе и получит другой ответ.
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 августа.
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 — накрывает ноль (см. множественные сравнения).
В учебной базе ответ к тому же известен: генератор задаёт оплату независимо от активации, так что находка ложная. Это свойство учебных данных; в своём продукте связь проверяют тем же расчётом с поправкой на число срезов, а причинность — экспериментом. Фразу «низкая активация съедает оплаты» эти данные не поддерживают.
| Канал | Создали пространство | Не создали | Разница, п. п. (95%-й интервал) |
|---|---|---|---|
| referral | 30,59% (104 из 340) | 33,94% (37 из 109) | −3,36 (от −13,51 до 6,79) |
| organic | 25,78% (132 из 512) | 25,00% (67 из 268) | 0,78 (от −5,64 до 7,20) |
| partner | 22,36% (36 из 161) | 20,13% (31 из 154) | 2,23 (от −6,80 до 11,26) |
| paid_search | 14,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.
доля платящих × выручка на платящего = 52 / 482 × 24,00 = 2,59 доллараПлатный поиск, регистрации июля. У рекомендаций то же произведение: 114 / 352 × 24,18 = 7,83.
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.
| Канал | Доля оплативших, п. п. (регистрации 1 июля – 9 августа) | Выручка за 30 дней, доллары (регистрации июля) |
|---|---|---|
| partner | 10,48 (от 5,34 до 15,62) | 2,19 (от 0,77 до 3,60) |
| organic | 14,72 (от 10,81 до 18,64) | 4,11 (от 2,95 до 5,28) |
| referral | 20,61 (от 15,68 до 25,55) | 5,24 (от 3,81 до 6,67) |
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.43import 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.
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, ДРР и срок окупаемости.
| Цена регистрации, доллары | Расходы на 482 регистрации | Выручка за 30 дней | Возврат |
|---|---|---|---|
| 1,00 | 482 | 1 248 | 259% |
| 2,59 | 1 248 | 1 248 | 100% |
| 3,00 | 1 446 | 1 248 | 86% |
| 5,00 | 2 410 | 1 248 | 52% |
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 по когортам и каналам.
Материалы по теме

Средний чек: формула, как считать и как его правильно читать
Средний чек — выручка, делённая на число заказов. Формула, расчёт в SQL по месяцам и каналам, медиана, эффект структуры и отличие от ARPU и ARPPU.
A/B-тест в SQL: конверсия, проверка сплита и разбор по сегментам
Как посчитать A/B-тест запросом: конверсия по группам, проверка SRM, разница, z и доверительный интервал в SQL и срез по устройствам, после которого меняется решение.
Процент и доля в SQL: от общего, в группе и прирост к прошлому месяцу
Как посчитать процент в SQL: доля от общего через SUM() OVER (), доля внутри группы, конверсия, прирост к прошлому месяцу через LAG, процентные пункты и округление.