Как посчитать CAC в SQL
price = 100 и discount = NULL. Что вернёт выражение SELECT price + discount FROM products для этой строки?Содержание:
Зачем аналитику считать CAC в SQL
CAC — первое число, которое спрашивают инвесторы, CMO и CFO. Отношение LTV / CAC показывает, работает ли юнит-экономика. Если CAC выше, чем LTV × маржа — каждый клиент убыточен; если CAC сильно ниже — вы, скорее всего, недоинвестируете и уступаете рынок конкурентам.
На словах CAC простой: «spend / acquired». На практике вопросов сразу становится много. Считаем blended или paid-only? Как распределить спенд между каналами? Что делать с органикой, которая пересекалась с платными показами? Какое окно атрибуции — 7, 28 дней, всё время? Ответы меняют картину, и плохая постановка задачи даёт некорректные решения о перераспределении бюджета.
В этой статье — восемь готовых SQL-запросов под типовые задачи маркетинг-аналитика:
- blended CAC (весь маркетинг / все новые пользователи)
- paid-only CAC
- CAC по каналам и по когортам
- CAC по факту первой покупки (а не регистрации)
- LTV / CAC в разрезе канала
- fully-loaded CAC (с зарплатами и инструментами)
- linear-attribution CAC по multi-touch-касаниям
Схема таблиц: ad_spend(day, channel, spend), users(user_id, signup_at, attribution_channel) и опционально first_orders(user_id, first_order_at).
Что такое CAC
CAC (Customer Acquisition Cost) — стоимость привлечения одного клиента.
CAC = Marketing spend / New customers acquiredВариаций формулы несколько:
- Blended CAC — весь маркетинговый бюджет делим на всех новых пользователей, включая органику.
- Paid CAC — только платный трафик, используется для оценки эффективности рекламы.
- Fully-loaded CAC — учитывает зарплаты маркетинговой команды, платформы и инструменты. Такой CAC любят инвесторы и CFO.
Схема данных
ad_spend (day, channel, spend)
users (user_id, signup_at, attribution_channel)
first_orders (user_id, first_order_at) -- из orders1. Blended CAC
Весь маркетинговый спенд за период / все новые клиенты за тот же период:
WITH spend_total AS (
SELECT SUM(spend) AS total_spend
FROM ad_spend
WHERE day BETWEEN '2026-01-01' AND '2026-03-31'
),
new_users AS (
SELECT COUNT(*) AS new_users_cnt
FROM users
WHERE signup_at BETWEEN '2026-01-01' AND '2026-03-31'
)
SELECT
st.total_spend,
nu.new_users_cnt,
st.total_spend::NUMERIC / NULLIF(nu.new_users_cnt, 0) AS blended_cac
FROM spend_total st, new_users nu;Blended CAC удобен для обзора, но прячет реальную эффективность рекламы: если половина пользователей пришла органически, «blended» CAC получается намного лучше, чем настоящий стоимость привлечения платного трафика.
2. CAC по каналам
Чтобы понять, где рекламный рубль работает лучше:
WITH spend_by_channel AS (
SELECT channel, SUM(spend) AS total_spend
FROM ad_spend
WHERE day BETWEEN '2026-01-01' AND '2026-03-31'
GROUP BY channel
),
users_by_channel AS (
SELECT attribution_channel AS channel, COUNT(*) AS new_users
FROM users
WHERE signup_at BETWEEN '2026-01-01' AND '2026-03-31'
GROUP BY attribution_channel
)
SELECT
sbc.channel,
sbc.total_spend,
ubc.new_users,
sbc.total_spend::NUMERIC / NULLIF(ubc.new_users, 0) AS cac
FROM spend_by_channel sbc
LEFT JOIN users_by_channel ubc USING (channel)
ORDER BY cac;Не забывайте, что attribution_channel — результат вашей модели атрибуции. Last-click и multi-touch дадут разные распределения.
3. CAC по когортам
Когортный CAC точнее: сравниваем спенд и пользователей того же месяца.
WITH cohorts AS (
SELECT
DATE_TRUNC('month', signup_at) AS cohort_month,
attribution_channel,
COUNT(*) AS new_users
FROM users
GROUP BY 1, 2
),
spend AS (
SELECT
DATE_TRUNC('month', day) AS month,
channel,
SUM(spend) AS spend
FROM ad_spend
GROUP BY 1, 2
)
SELECT
c.cohort_month,
c.attribution_channel,
c.new_users,
s.spend,
s.spend::NUMERIC / NULLIF(c.new_users, 0) AS cac
FROM cohorts c
LEFT JOIN spend s
ON s.month = c.cohort_month
AND s.channel = c.attribution_channel
ORDER BY c.cohort_month, c.attribution_channel;Дополнительная польза: видно, как меняется CAC канала от месяца к месяцу. Если он ползёт вверх — конкуренция выросла или аудитория выгорает.
4. Paid-only CAC
Отфильтровываем органику и прямые заходы:
WITH paid_spend AS (
SELECT SUM(spend) AS paid_spend
FROM ad_spend
WHERE channel IN ('google_ads', 'yandex_direct', 'vk_ads', 'facebook_ads')
AND day BETWEEN '2026-01-01' AND '2026-03-31'
),
paid_users AS (
SELECT COUNT(*) AS paid_users
FROM users
WHERE attribution_channel IN ('google_ads', 'yandex_direct', 'vk_ads', 'facebook_ads')
AND signup_at BETWEEN '2026-01-01' AND '2026-03-31'
)
SELECT
paid_spend.paid_spend::NUMERIC / NULLIF(paid_users.paid_users, 0) AS paid_cac
FROM paid_spend, paid_users;Список «платных» каналов стоит вынести в справочник или dbt-макрос — с ростом продукта каналов становится больше, и одна копипаста превращается в источник ошибок.
5. CAC по первой покупке
Более строгий CAC: считаем не регистрации, а платящих клиентов.
WITH spend AS (
SELECT
channel,
DATE_TRUNC('month', day) AS month,
SUM(spend) AS spend
FROM ad_spend
GROUP BY 1, 2
),
first_buyers AS (
SELECT
u.attribution_channel AS channel,
DATE_TRUNC('month', fo.first_order_at) AS month,
COUNT(*) AS new_buyers
FROM first_orders fo
JOIN users u ON u.user_id = fo.user_id
GROUP BY 1, 2
)
SELECT
s.channel,
s.month,
s.spend,
fb.new_buyers,
s.spend::NUMERIC / NULLIF(fb.new_buyers, 0) AS cac_per_buyer
FROM spend s
LEFT JOIN first_buyers fb
ON fb.channel = s.channel AND fb.month = s.month
ORDER BY s.month, cac_per_buyer;В SaaS-продуктах часто именно эта метрика попадает в отчётность — «cost per paying customer», — потому что бесплатные signup'ы в unit-экономику считать не совсем честно.
6. LTV / CAC в разрезе канала
Главное сравнение для маркетинга:
WITH spend_by_channel AS (
SELECT channel, SUM(spend) AS total_spend
FROM ad_spend
GROUP BY channel
),
users_by_channel AS (
SELECT attribution_channel AS channel, COUNT(*) AS new_users
FROM users
GROUP BY attribution_channel
),
cac_channel AS (
-- агрегируем спенд и пользователей независимо и только потом делим,
-- иначе после JOIN одна строка spend задвоится на число пользователей в канале
SELECT
s.channel,
s.total_spend::NUMERIC / NULLIF(u.new_users, 0) AS cac
FROM spend_by_channel s
LEFT JOIN users_by_channel u USING (channel)
),
ltv_channel AS (
SELECT
u.attribution_channel AS channel,
AVG(user_ltv.total_revenue) AS avg_ltv
FROM users u
JOIN (
SELECT user_id, SUM(total) AS total_revenue
FROM orders
WHERE status = 'paid'
GROUP BY user_id
) user_ltv ON user_ltv.user_id = u.user_id
GROUP BY u.attribution_channel
)
SELECT
c.channel,
c.cac,
l.avg_ltv,
l.avg_ltv / NULLIF(c.cac, 0) AS ltv_cac_ratio
FROM cac_channel c
JOIN ltv_channel l USING (channel)
ORDER BY ltv_cac_ratio DESC;Важный нюанс — агрегировать спенд и пользователей независимо и только потом делить. Если сделать JOIN на необработанных таблицах, одна строка спенда задвоится на количество пользователей в канале — и CAC окажется в десятки раз выше реального.
7. Fully-loaded CAC
Включает не только рекламу, но и зарплаты маркетинговой команды, лицензии инструментов и другие накладные расходы:
WITH all_marketing_cost AS (
-- рекламный спенд
SELECT SUM(spend) AS cost
FROM ad_spend
WHERE day BETWEEN '2026-01-01' AND '2026-03-31'
UNION ALL
-- зарплаты команды
SELECT SUM(monthly_salary)
FROM marketing_payroll
WHERE month BETWEEN '2026-01-01' AND '2026-03-31'
UNION ALL
-- SaaS и инструменты
SELECT SUM(cost)
FROM marketing_tools
WHERE month BETWEEN '2026-01-01' AND '2026-03-31'
),
total_cost AS (
SELECT SUM(cost) AS total_marketing_cost FROM all_marketing_cost
),
new_users AS (
SELECT COUNT(*) AS new_users_cnt
FROM users
WHERE signup_at BETWEEN '2026-01-01' AND '2026-03-31'
)
SELECT
tc.total_marketing_cost,
nu.new_users_cnt,
tc.total_marketing_cost::NUMERIC / NULLIF(nu.new_users_cnt, 0) AS fully_loaded_cac
FROM total_cost tc, new_users nu;Fully-loaded CAC почти всегда заметно выше blended — и это нормально. Именно эту цифру разумно использовать в board-репортах.
8. Linear attribution
Last-click — самый простой, но не самый справедливый способ. Linear attribution в SQL:
-- каждому касанию до регистрации присваиваем вес 1 / число касаний пользователя
WITH touches AS (
SELECT
t.user_id,
t.channel,
t.touched_at
FROM marketing_touches t
JOIN users u ON u.user_id = t.user_id
WHERE t.touched_at <= u.signup_at
),
user_touch_counts AS (
SELECT user_id, COUNT(*) AS total_touches
FROM touches
GROUP BY user_id
),
attributed AS (
SELECT
t.user_id,
t.channel,
1.0 / utc.total_touches AS attribution_weight
FROM touches t
JOIN user_touch_counts utc USING (user_id)
)
SELECT
channel,
SUM(attribution_weight) AS attributed_users
FROM attributed
GROUP BY channel
ORDER BY attributed_users DESC;Дальше полученные «атрибуцированные пользователи» на канал подставляем вместо сырого COUNT(*) в расчёт CAC.
Частые ошибки
1. Путать blended и paid
Blended ≠ paid. Если 50% пользователей приходят органически, blended CAC = paid CAC / 2 — выглядит лучше, чем есть. В board-отчётах обязательно подписываем, какой CAC показываем.
2. Сравнивать CAC и LTV без маржи
LTV 3000 ₽ и CAC 1000 ₽ выглядят как 3:1. Но при 30% марже настоящий payoff — LTV 900 ₽ при CAC 1000 ₽, то есть убыток. Сравнивайте с маржой или с gross profit per user.
3. Забыть про NULLIF
spend / new_users -- упадёт при new_users = 0
spend / NULLIF(new_users, 0) -- безопасно, вернёт NULL4. Смешивать регистрации и покупки
CAC per signup ≠ CAC per paying customer. Для SaaS и многих подписочных продуктов отчёт держится на second.
5. Игнорировать окно атрибуции
Если пользователь кликнул рекламу в январе, а купил в марте, у разных команд будут разные мнения, кому её засчитать. Окно 7 или 28 дней — индустриальный стандарт, но зафиксируйте это в определении метрики и не меняйте его ретроспективно.
Связанные темы
- CAC простыми словами
- Как посчитать LTV в SQL
- Unit-экономика простыми словами
- Attribution простыми словами
FAQ
Blended или paid CAC — что важнее?
Paid — для решений по бюджету рекламы. Blended — для общей юнит-экономики и board-репортинга. В идеале показывайте обе.
Нужен ли fully-loaded CAC?
Для малого бизнеса часто хватает paid + немного overhead. Для инвесторов и enterprise — обязательно, потому что зарплаты и инструменты сильно меняют картину.
Как учитывать длинное окно атрибуции?
Зафиксируйте разумный дефолт (например, 28 дней) и параллельно держите несколько моделей атрибуции для сравнения. Не меняйте окно задним числом — иначе сравнения с историей становятся бессмысленными.
CAC должен быть меньше LTV?
Общая рекомендация — LTV / CAC ≥ 3:1. Ниже — проблема либо в CAC, либо в LTV. Слишком высокая ratio (5:1 и выше) часто означает, что вы недоинвестируете и пропускаете рост.
Куда относить бренд-кампании?
Чаще всего — в fully-loaded CAC: это инвестиция в долгосрочное привлечение, и она редко поддаётся точной атрибуции к конкретным пользователям.