Как посчитать ROAS в SQL
user_id (тип int) и session_id (тип text). Какой вариант корректен для конкатенации в SQL?Содержание:
Зачем ROAS
Маркетинг каждую неделю отчитывается: «потратили 1 млн на рекламу, принесли 4 млн выручки». Звучит хорошо. Но ROAS 4 для нового бренда — это неплохо, а для зрелого e-commerce с маржой 15% — провал (маржа с 4 млн = 600 тыс., минус 1 млн на трафик = −400 тыс. убытка).
Аналитика просят: «ROAS по каналам», «ROAS Performance Max vs Search», «ROAS на окне 7 дней против 30». В статье — SQL и нюансы (атрибуция, окно атрибуции, ROAS ≠ ROI).
Формула ROAS
ROAS = Revenue from ads / Ad spendROAS = 4 значит «1 рубль рекламы вернул 4 рубля выручки». ROAS = 1 — точка безубыточности (по выручке, не по марже).
Часто ROAS выражают коэффициентом (4.0) или процентом (400%). Это одно и то же.
Базовый расчёт
Данные: ad_spend(date, channel, spend), attributed_revenue(date, channel, revenue).
WITH spend AS (
SELECT channel, SUM(spend) AS total_spend
FROM ad_spend
WHERE DATE >= '2026-04-01' AND DATE < '2026-05-01'
GROUP BY channel
),
revenue AS (
SELECT channel, SUM(revenue) AS total_revenue
FROM attributed_revenue
WHERE DATE >= '2026-04-01' AND DATE < '2026-05-01'
GROUP BY channel
)
SELECT
s.channel,
s.total_spend,
r.total_revenue,
r.total_revenue::NUMERIC / NULLIF(s.total_spend, 0) AS roas
FROM spend s
JOIN revenue r ON r.channel = s.channel
ORDER BY roas DESC;Важно: агрегировать каждую сторону отдельно, а потом соединять через JOIN. JOIN до агрегации может размножить строки.
ROAS по каналам и кампаниям
WITH spend AS (
SELECT channel, campaign, SUM(spend) AS spend_amt
FROM ad_spend
WHERE DATE >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY channel, campaign
),
rev AS (
SELECT channel, campaign, SUM(revenue) AS revenue_amt
FROM attributed_revenue
WHERE DATE >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY channel, campaign
)
SELECT
COALESCE(s.channel, r.channel) AS channel,
COALESCE(s.campaign, r.campaign) AS campaign,
s.spend_amt,
r.revenue_amt,
r.revenue_amt::NUMERIC / NULLIF(s.spend_amt, 0) AS roas
FROM spend s
FULL OUTER JOIN rev r
ON r.channel = s.channel AND r.campaign = s.campaign
ORDER BY roas DESC NULLS LAST;FULL OUTER JOIN показывает и кампании с тратами, но без выручки (плохие), и наоборот — с выручкой, но без трат (баги атрибуции).
Окно и модель атрибуции
ROAS зависит от окна атрибуции. Купил через 2 дня после клика — это «привлёк маркетинг». Через 60 дней — спорно.
Окно атрибуции 7 дней
WITH ad_clicks AS (
SELECT user_id, channel, campaign, click_date
FROM ad_events
WHERE event_type = 'click'
),
purchases AS (
SELECT user_id, total, purchase_date
FROM orders
WHERE status = 'paid'
)
SELECT
c.channel,
c.campaign,
SUM(p.total) AS attributed_revenue_7d
FROM ad_clicks c
JOIN purchases p
ON p.user_id = c.user_id
AND p.purchase_date >= c.click_date
AND p.purchase_date < c.click_date + INTERVAL '7 days'
GROUP BY c.channel, c.campaign;Last-click vs first-click
Last-click: считаем покупку каналу последнего клика. Самая частая модель — но завышает ретаргетинг.
WITH last_click AS (
SELECT
user_id,
channel,
click_date,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY click_date DESC) AS rn
FROM ad_events
WHERE event_type = 'click'
)
SELECT
lc.channel,
SUM(o.total) AS revenue
FROM orders o
JOIN last_click lc ON lc.user_id = o.user_id AND lc.rn = 1
WHERE o.status = 'paid'
GROUP BY lc.channel;First-click оценивает «верх воронки», а линейная и time-decay — компромиссные модели.
ROAS vs ROI vs MER
| Метрика | Формула | Что показывает |
|---|---|---|
| ROAS | revenue / ad spend | возврат выручки на каждый рубль рекламы |
| ROI | (revenue × margin - spend) / spend | прибыль на каждый рубль (с учётом маржи) |
| MER (Marketing Efficiency Ratio) | total revenue / total marketing spend | общая эффективность маркетинга |
ROAS 4 при марже 20% — это ROI = (4 × 0,2 - 1) / 1 = -20%, убыток. ROAS — выручка, ROI — прибыль.
MER считает по всей выручке (включая органику), удобен для оценки всего маркетинга, а не одного канала.
Частые ошибки
Ошибка 1. ROAS без учёта маржи
«ROAS 4 — отлично!» Если маржа 15%, это убыток. Всегда показывайте бизнесу ROAS вместе с маржой или сразу конвертируйте в ROI.
Ошибка 2. JOIN до агрегации
Если у канала 100 кликов и 30 покупок и сделать JOIN до агрегации — получишь 100×30 = 3000 строк, и SUM раздувается. Сначала агрегаты, потом JOIN — см. базовый пример.
Ошибка 3. NULL вместо выручки у каналов с тратами
Кампания запущена, потратила деньги, но ни одной покупки. INNER JOIN потеряет её. Используйте FULL OUTER JOIN + COALESCE(revenue, 0) для честного среднего.
Ошибка 4. Не учитывать окно атрибуции
Сравнивать ROAS Google Ads (7 дней) с Facebook (28 дней) некорректно. Зафиксируйте одно окно для всех каналов.
Ошибка 5. Двойной учёт выручки
Если один заказ атрибутирован двум кампаниям — выручка считается дважды. Решение: дедупликация по user_id × order_id или одна модель атрибуции на отчёт.
Ошибка 6. Считать ROAS = 1 точкой безубыточности
Это безубыточность по выручке, но не по прибыли. Для маржи 30% безубыточный ROAS = 1/0,3 ≈ 3,3.
Связанные темы
FAQ
Какой ROAS считается хорошим?
Зависит от маржи. Если маржа 20% — break-even ROAS = 5, цель ROAS 8-10. Маржа 50% — break-even ROAS = 2, цель 3-4. Просите данные о марже у финансистов.
ROAS у новых vs повторных покупателей одинаков?
Нет. Повторные — обычно сильно выше, но они и так бы купили. Считайте ROAS на новых пользователях (new customer ROAS) — это «честная» метрика рекламы.
Как мерить ROAS в B2B с долгим циклом сделки?
Стандартный ROAS не работает — клик в январе, сделка в июне. Считают pipeline ROAS (по сумме открытых сделок) или используют длинное окно (90-180 дней) с проверкой через когорты.
ROAS в реальном времени или с задержкой?
С задержкой 24-48 часов (атрибуция «дозревает» — конверсии приходят после кликов). Real-time дашборды показывают направление тренда, а не финальные числа.
Что такое target ROAS в Google Ads?
Автоматическая стратегия: алгоритм оптимизирует ставки так, чтобы среднее ROAS было ≥ заданного. Работает только при достаточном объёме конверсий (50+ в месяц по кампании).