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

Проверь себя · 1/3разбор после ответа
Какое утверждение про RIGHT JOIN верно в аналитических запросах?

Зачем аналитику считать 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.

CAC в разных отраслях

Стоимость привлечения и то, с чем её сравнивать, зависит от модели бизнеса — на собесе это уточняют до расчёта.

SaaS B2B. Цикл сделки длинный, поэтому в CAC входят зарплаты отдела продаж (sales) и пресейла — считают fully-loaded CAC и меряют окупаемость в месяцах (CAC payback). Отчётность держится на «cost per paying customer», а не на стоимости регистрации.

E-commerce и D2C. CAC считают на первого покупателя и жёстко разделяют blended и paid: при большой доле органики blended выглядит красиво, а платный канал может быть убыточным. Окупается CAC повторными покупками, поэтому его читают вместе с удержанием (retention).

Мобильные приложения. Разрыв между стоимостью установки (CPI) и стоимостью платящего огромен из-за низкой конверсии в платящих — CAC на install и CAC на платящего отличаются в разы, путать их нельзя.

Маркетплейсы. CAC покупателя и продавца считают отдельно: один активный продавец окупается выручкой множества покупателей, поэтому его привлечение живёт по своей экономике.

Схема данных

ad_spend        (day, channel, spend)
users           (user_id, signup_at, attribution_channel)
first_orders    (user_id, first_order_at)   -- из orders

1. 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-макрос — с ростом продукта каналов становится больше, и одна копипаста превращается в источник ошибок.

Закрепи формулу CAC в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать CAC в Telegram

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», — потому что бесплатные регистрации в юнит-экономику считать не совсем честно.

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 — и это нормально. Именно эту цифру разумно показывать в отчётах для инвесторов и совета директоров.

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.

Примеры с собеседований

CAC на собесе проверяют на понимании, что входит в затраты и с чем метрику сравнивать.

«Что входит в CAC кроме рекламного бюджета?» Слабый ответ — только спенд на рекламу. Сильный: зарплаты маркетинга и продаж, инструменты и платформы, продакшн креативов — это fully-loaded CAC, и именно он идёт в отчёты для инвесторов. CAC без учёта команды систематически занижен.

«Blended или paid CAC — что использовать?» Сильный ответ: paid — для решений по рекламному бюджету, blended — для общей юнит-экономики и отчётности. Показывать лучше обе, потому что blended маскирует эффективность платных каналов при большой доле органики.

«CAC вырос в два раза — как найдёшь причину?» Сильный ответ — декомпозиция: разрез по каналам (какой именно подорожал), не сместился ли микс в сторону дорогих каналов, не упала ли конверсия, не выгорела ли аудитория. Прогнать такие вопросы с разбором можно в Карьернике.

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

1. Путать blended и paid

Blended ≠ paid. Если 50% пользователей приходят органически, blended CAC = paid CAC / 2 — выглядит лучше, чем есть. В отчётах для инвесторов обязательно подписываем, какой CAC показываем.

2. Сравнивать CAC и LTV без маржи

LTV 3000 ₽ и CAC 1000 ₽ выглядят как 3:1. Но при 30% марже реальная отдача — LTV 900 ₽ при CAC 1000 ₽, то есть убыток. Сравнивайте с маржой или с gross profit per user.

3. Забыть про NULLIF

spend / new_users            -- упадёт при new_users = 0
spend / NULLIF(new_users, 0) -- безопасно, вернёт NULL

4. Смешивать регистрации и покупки

CAC per signup ≠ CAC per paying customer. Для SaaS и многих подписочных продуктов отчёт держится именно на платящих клиентах.

5. Игнорировать окно атрибуции

Если пользователь кликнул рекламу в январе, а купил в марте, у разных команд будут разные мнения, кому её засчитать. Окно 7 или 28 дней — индустриальный стандарт, но зафиксируйте это в определении метрики и не меняйте его ретроспективно.

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

FAQ

Blended или paid CAC — что важнее?

Paid CAC нужен для решений по рекламному бюджету — он показывает реальную цену платного трафика. Blended берут для общей юнит-экономики и отчётности перед инвесторами. В идеале показывайте обе цифры: blended в одиночку маскирует эффективность платных каналов при большой доле органики.

Нужен ли fully-loaded CAC?

Для малого бизнеса часто хватает paid CAC плюс немного накладных расходов. Для инвесторов и крупного бизнеса fully-loaded CAC обязателен: зарплаты команды и стоимость инструментов сильно меняют итоговую цифру.

Как учитывать длинное окно атрибуции?

Зафиксируйте разумный дефолт (например, 28 дней) и параллельно держите несколько моделей атрибуции для сравнения. Не меняйте окно задним числом — иначе сравнения с историей становятся бессмысленными.

CAC должен быть меньше LTV?

Общая рекомендация — держать LTV / CAC ≥ 3:1. Если отношение ниже, проблема либо в дорогом привлечении, либо в слабом LTV. Но и слишком высокое соотношение (5:1 и выше) — тревожный сигнал: часто оно означает, что вы недоинвестируете в рост и уступаете рынок конкурентам.

Куда относить бренд-кампании?

Чаще всего — в fully-loaded CAC: это инвестиция в долгосрочное привлечение, и она редко поддаётся точной атрибуции к конкретным пользователям.