Как посчитать ROI в SQL
Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.
Зачем это нужно аналитику
ROI — главная метрика «стоит ли вкладываться». Маркетинг хочет знать, какие каналы окупаются. Продуктовая команда — стоил ли тот редизайн трёх месяцев разработки. Финансовый директор — какой возврат на софт для аналитики за 10 000$ в год. Все эти вопросы — ROI в разных одеждах.
Главная путаница: ROI ≠ ROAS. ROAS (Return on Ad Spend) = Revenue / Spend — грубая метрика, показывает выручку на рубль рекламы. ROI учитывает маржу и все расходы: ROI = (Profit − Investment) / Investment. ROAS 3 может быть убыточным, если margin 20% (выручка 3₽, profit 0.6₽, investment 1₽ → ROI = -40%).
На работе аналитика регулярно просят: «посчитай ROI кампании», «какой feature ROI в этом квартале», «сколько принесла скидочная акция». Без правильного SQL — выводы неверные.
В статье — готовые запросы:
- Базовый ROI маркетинговой кампании
- ROI по каналам и кампаниям
- Feature ROI (value - cost of development)
- ROI с учётом LTV (incremental)
- ROI по когортам во времени
- ROI vs ROAS — два отдельных запроса рядом
Схемы: ad_spend, orders, feature_usage.
1. Базовая формула
ROI = (Gain - Investment) / Investment × 100%Пример: потратили 100 000 ₽ на рекламу, получили 150 000 ₽ profit → ROI = 50%.
2. ROI маркетинговой кампании
WITH campaign_revenue AS (
SELECT
o.attribution_campaign AS campaign,
SUM(o.total * 0.25) AS gross_profit -- 25% margin
FROM orders o
WHERE o.attribution_campaign = 'spring_sale'
AND o.created_at >= '2026-04-01'
AND o.status = 'paid'
GROUP BY 1
),
campaign_spend AS (
SELECT
campaign,
SUM(spend) AS total_spend
FROM ad_spend
WHERE campaign = 'spring_sale'
AND day >= '2026-04-01'
GROUP BY 1
)
SELECT
cr.campaign,
cs.total_spend AS investment,
cr.gross_profit AS gain,
(cr.gross_profit - cs.total_spend) AS net_profit,
(cr.gross_profit - cs.total_spend) / NULLIF(cs.total_spend, 0) * 100 AS roi_pct
FROM campaign_revenue cr
JOIN campaign_spend cs USING (campaign);3. ROI по каналам
WITH by_channel AS (
SELECT
channel,
SUM(spend) AS spend,
SUM(revenue * 0.25) AS profit -- margin
FROM (
SELECT u.attribution_channel AS channel,
SUM(o.total) AS revenue,
0 AS spend
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.created_at >= '2026-01-01'
AND o.status = 'paid'
GROUP BY 1
UNION ALL
SELECT channel, 0, SUM(spend)
FROM ad_spend
WHERE day >= '2026-01-01'
GROUP BY 1
) t
GROUP BY channel
)
SELECT
channel,
spend,
profit,
(profit - spend) / NULLIF(spend, 0) * 100 AS roi_pct
FROM by_channel
WHERE spend > 0
ORDER BY roi_pct DESC;4. ROI с учётом LTV (incremental)
Краткосрочный ROI обманывает: одна покупка в первый месяц, но пользователь возвращается 12 месяцев. Включаем LTV:
WITH cohort_ltv AS (
SELECT
DATE_TRUNC('month', first_order_at) AS acquired_month,
u.attribution_channel AS channel,
AVG(lifetime_revenue * 0.25) AS avg_profit_per_user -- margin
FROM users u
JOIN (
SELECT user_id, MIN(created_at) AS first_order_at, SUM(total) AS lifetime_revenue
FROM orders WHERE status = 'paid'
GROUP BY user_id
) user_rev ON user_rev.user_id = u.id
GROUP BY 1, 2
),
spend_per_month AS (
SELECT DATE_TRUNC('month', day) AS month, channel, SUM(spend) AS spend
FROM ad_spend
GROUP BY 1, 2
),
users_acquired AS (
SELECT DATE_TRUNC('month', signup_at) AS month, attribution_channel AS channel,
COUNT(*) AS acquired
FROM users
GROUP BY 1, 2
)
SELECT
cl.acquired_month,
cl.channel,
cl.avg_profit_per_user * ua.acquired AS total_profit,
sm.spend AS investment,
(cl.avg_profit_per_user * ua.acquired - sm.spend) / NULLIF(sm.spend, 0) * 100 AS ltv_roi_pct
FROM cohort_ltv cl
JOIN spend_per_month sm ON sm.month = cl.acquired_month AND sm.channel = cl.channel
JOIN users_acquired ua ON ua.month = cl.acquired_month AND ua.channel = cl.channel;5. Feature ROI
ROI на продуктовые фичи. Investment = часы разработки × rate. Gain = incremental revenue.
-- упрощённо: предположим value фичи = revenue пользователей, которые её используют
WITH feature_users AS (
SELECT DISTINCT user_id FROM feature_usage WHERE feature_name = 'new_checkout'
),
feature_revenue AS (
SELECT SUM(o.total) * 0.25 AS profit -- margin
FROM orders o
WHERE o.user_id IN (SELECT user_id FROM feature_users)
AND o.created_at >= '2026-04-01'
AND o.status = 'paid'
),
feature_investment AS (
SELECT 40 * 160 * 2500 AS dev_cost -- 40 hours × 160 rate × 2.5 месяца ≈ 1.6M
)
SELECT
profit,
dev_cost AS investment,
(profit - dev_cost) / NULLIF(dev_cost, 0) * 100 AS feature_roi_pct
FROM feature_revenue, feature_investment;Caveat: это не incremental — нужно A/B-тест, чтобы понять, что фича реально дала прирост.
6. ROI vs ROAS — разница
SELECT
campaign,
SUM(revenue) AS revenue,
SUM(spend) AS spend,
-- ROAS — просто revenue / spend
SUM(revenue) / NULLIF(SUM(spend), 0) AS roas,
-- ROI учитывает margin
(SUM(revenue) * 0.25 - SUM(spend)) / NULLIF(SUM(spend), 0) * 100 AS roi_pct
FROM campaign_stats
GROUP BY campaign;ROAS 4 + margin 25% → ROI = 0%. Ноль прибыли. ROAS 4 + margin 30% → ROI = 20%.
Отсюда правило: breakeven ROAS = 1 / margin. При margin 25% breakeven ROAS = 4.
7. ROI бизнес-решения
Пример: стоит ли нанять customer success менеджера для enterprise?
-- investment: salary + benefits, 12 месяцев
-- gain: снижение churn × avg MRR
WITH churn_reduction AS (
SELECT
150 AS enterprise_customers, -- количество enterprise
0.05 AS current_monthly_churn, -- 5%
0.03 AS expected_monthly_churn, -- 3% после найма CSM
50000 AS avg_mrr_per_customer,
1500000 AS csm_annual_cost -- 1.5M в год
)
SELECT
enterprise_customers * avg_mrr_per_customer *
(current_monthly_churn - expected_monthly_churn) * 12 AS annual_saved_revenue,
csm_annual_cost AS investment,
(enterprise_customers * avg_mrr_per_customer *
(current_monthly_churn - expected_monthly_churn) * 12 - csm_annual_cost) /
NULLIF(csm_annual_cost, 0) * 100 AS roi_pct
FROM churn_reduction;Частые ошибки
Путать ROI и ROAS
ROAS — выручка. ROI — profit (с margin).
Не учитывать LTV
Краткосрочный ROI отрицательный, долгосрочный — положительный. Нужны оба.
Не считать incremental
Post-hoc ROI часто завышен. Правильная проверка — A/B.
Игнорировать overhead
Кроме прямых затрат есть operational overhead. Fully-loaded ROI реалистичнее.
Связанные темы
FAQ
ROI в процентах или в долях?
Обычно в процентах. ROI = 0.5 → 50%.
Margin не знаем — как считать?
Уточните у финансов. Без margin — ROAS, не ROI.
Когда ROI отрицательный — бросать канал?
Если ROI считается без LTV — возможно канал окупается позже.
Можно ли ROI для бренд-рекламы?
Сложно. Brand awareness эффекты долгие. Обычно ROI только для performance.
Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.