12
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Оцениваем рекламные кампании с помощью SQL

Время чтения 31 минута
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
12
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

Приветствую специалистов по данным!

Я Павел Беляев, тимлид группы обработки данных в дата-офисе сервиса eLama и автор канала «Тимлидское об аналитике». Мы разрабатываем и поддерживаем витрины данных на языке SQL. На базе этих витрин строятся красивые отчеты в средствах визуализации. В этой статье я покажу, как построить на SQL-витрину одного из самых популярных отчетов, знакомых каждому маркетологу, — отчет об эффективности рекламных кампаний.

Оцениваем рекламные кампании с помощью SQL

Постановка задачи

Специалист по рекламе ведет кампании на разных площадках, привлекая трафик на сайт компании, оказывающей некие услуги, или продающей товары, или предоставляющей платную онлайн-функциональность. Как правило, посетителю для начала работы необходимо сначала зарегистрироваться на сайте, иногда подтвердить контактную информацию и затем приступить к покупкам. Таким образом, воронка продаж выглядит так: посещение — регистрация — активация (то есть первый платеж). Маркетолог для каждой кампании хочет знать:

  • объем трафика;
  • стоимость трафика;
  • количество микроконверсий (то есть промежуточных шагов к главной цели);
  • количество целевых действий;
  • эффективность привлечения.

Кроме того, ему интересна динамика во времени, то есть нужен столбец с датой. Состав колонок витрины такой:

  • date — дата;
  • channel — источник и канал трафика (например, “yandex / cpc”);
  • campaign — имя кампании (обычно заданное в метке utm_campaign);
  • impressions — количество показов объявления;
  • ctr — Click Through Rate, показатель кликабельности;
  • clicks — количество кликов по объявлению;
  • cost — расходы на кампанию;
  • cpc — Cost Per Click, стоимость клика;
  • cr — Conversion Rate, коэффициент конверсии из клика в регистрацию;
  • regs — количество регистраций;
  • cpr — Cost Per Registration, стоимость регистрации;
  • acts — количество активаций;
  • cpa — Cost Per Activation, стоимость активации.

То есть имеем воронку от показов до первого платежа, а также конверсию между шагами и показатели стоимости, так? Не совсем.

Дело в первой колонке — date. Витрина начинается с метрик кампании, значит, речь идет о дате показов, кликов и расходов. Но пользователь может совершить регистрацию далеко не в тот же день, как кликнул по объявлению, а активацию — еще позже. Покажу два способа построения витрины для учета этой «нестыковки».

Склейка по кампании и дате

Первый вариант — принять допущение, что большинство целевых действий совершается в день клика либо что среднее количество конверсий не меняется изо дня в день. Тогда мы просто делаем LEFT JOIN всех данных по кампании и дате.

При этом придется обойти подводный камень: может случиться, что в один день определенная кампания не крутилась, но зато зарегистрировался пользователь, который зашел с нее на сайт несколько дней назад. Такая регистрация не попадет в выборку, ведь мы используем LEFT JOIN к данным кампании. 

Поэтому используем FULL OUTER JOIN, а затем выберем не-NULL значение кампании.

WITH cost AS
(
    SELECT date, 
        CONCAT(utm_source, ' / ', utm_medium) AS channel,
        utm_campaign AS campaign,
        impressions, clicks, cost
    FROM costs -- данные о кампаниях
),
reg AS
(
    SELECT DATE(date_registration) AS date,
        CONCAT(utm_source, " / ", utm_medium) AS channel,
        utm_campaign AS campaign,
        COUNT(user_id) AS regs
    FROM registrations
    WHERE utm_medium = 'cpc' -- только кампании с оплатой за клик
    GROUP BY ALL
),
act AS
(
    SELECT date, channel, campaign,
        COUNT(user_id) AS acts -- количество активаций
    FROM
    (
        SELECT MIN(date_paid) AS date, -- вычисляем дату активации
            CONCAT(utm_source, " / ", utm_medium) AS channel,
            utm_campaign AS campaign,
            user_id
        FROM payments -- все платежи пользователя
        WHERE utm_medium = 'cpc' -- только кампании с оплатой за клик
        GROUP BY ALL
    )
    GROUP BY ALL
)
SELECT COALESCE(c.date, r.date, a.date) AS date,
    COALESCE(c.channel, r.channel, a.channel) AS channel,
    COALESCE(c.campaign, r.campaign, a.campaign) AS campaign,
    impressions, clicks, cost,
    regs,
    acts
FROM cost AS c
FULL OUTER JOIN reg AS r ON r.date = c.date
    AND r.channel = c.channel
    AND r.campaign = c.campaign
    
FULL OUTER JOIN act AS a ON a.date = c.date
    AND a.channel = c.channel
    AND a.campaign = c.campaign

 

Соединение FULL OUTER JOIN решит нашу проблему, правда? Нет. В этом примере если не будет данных в таблице cost, то регистрации подтянутся нормально, а активации — только отдельной строкой. Поэтому мы сделаем по-другому: выберем все кампании из cost и присвоим им дни через генератор дат, а затем присоединим к ним всё остальное.

SELECT COALESCE(c.date, r.date, a.date) AS date,
    COALESCE(c.channel, r.channel, a.channel) AS channel,
    COALESCE(c.campaign, r.campaign, a.campaign) AS campaign,
    impressions,
    clicks / impressions AS ctr,
    clicks, 
    cost / clicks AS cpc,
    cost,
    regs / clicks AS cr,
    regs,
    cost / regs AS cpr,
    acts,
    cost / acts AS cpa
FROM 
(
    SELECT arrayJoin(arrayMap(x -> toDate(x), -- генерируем даты (ClickHouse)
        range(toInt32(toStartOfDay(toDate('2025-01-01'))), -- дата начала изучаемого периода
             toInt32(now() + toIntervalDay(1)), 24 * 3600))) AS date,
        channel, campaign
    FROM
    (
        SELECT DISTINCT channel, campaign
        FROM cost
    )
) AS dc
LEFT JOIN cost AS c ON c.date = dc.date
    AND c.channel = dc.channel
    AND c.campaign = dc.campaign
LEFT JOIN reg AS r ON r.date = dc.date
    AND r.channel = dc.channel
    AND r.campaign = dc.campaign
    
LEFT JOIN act AS a ON a.date = dc.date
    AND a.channel = dc.channel
    AND a.campaign = dc.campaign
WHERE c.date IS NOT NULL -- оставляем только строки со значимыми данными
    OR r.date IS NOT NULL
    OR a.date IS NOT NULL
 

 

Этот подход годится, если длительность сделки невелика, а кампании не останавливаются. Он позволяет увидеть текущий, дневной снимок ситуации с рекламой и оперативно реагировать на изменения в показателях. Если же мы хотим более точной, более «правдивой» оценки эффективности кампаний, лучше применять другой подход.

Склейка по идентификатору пользователя

Для построения полноценной воронки необходимо, чтобы каждый новый ее шаг привязывался к предыдущему. Можно получить отчет по воронке, убрав из витрины дату и присоединив к имеющимся кампаниям количество действий, которые к ним относятся. Но это получится сводный отчет, не отражающий динамики кампаний, а она может сильно меняться.

В любом случае отчет по воронке будет когортным, а когорты будут строиться либо на основе только кампании, либо по кампании и дате клика. Разберем второе.

Теперь для нас недопустимо приравнивать дату регистрации к дате клика. Мы хотим знать, сколько принесла конверсий, в том числе отложенных, каждая кампания за каждый день. Для этого нам придется использовать данные инструмента веб-аналитики, например Google Analytics. К данным о кампаниях мы сначала присоединим данные о посещениях сайта в тот день, считая клик = посещение страницы. А уже к посещениям присоединим по идентификатору пользователя (точнее, его веб-клиента) регистрации. А к регистрациям — активации. Дата будет браться только из первого шага.

SELECT c.date AS date, 
    CONCAT(utm_source, ' / ', utm_medium) AS channel,
    utm_campaign AS campaign,
    SUM(c.impressions) AS impressions, 
    SUM(c.clicks) / SUM(c.impressions) AS ctr,
    SUM(c.clicks) AS clicks, 
    SUM(c.cost) / SUM(c.clicks) AS cpc,
    SUM(c.cost) AS cost,
    COUNT(DISTINCT r.user_id) / SUM(c.clicks) AS cr,
    COUNT(DISTINCT r.user_id) AS regs,
    SUM(c.cost) / COUNT(DISTINCT r.user_id) AS cpr,
    COUNT(DISTINCT p.user_id) AS acts,
    SUM(c.cost) / COUNT(DISTINCT p.user_id) AS cpa
FROM costs c -- данные о кампаниях
LEFT JOIN
(
    SELECT DISTINCT date,
        client_id, -- идентификатор клиента
        CONCAT(utm_source, ' / ', utm_medium) AS channel,
        utm_campaign AS campaign,
    FROM hits -- данные о хитах из системы веб-аналитики
) w ON w.date = c.date
    AND w.channel = c.channel
    AND w.campaign = c.campaign
LEFT JOIN
(
    SELECT client_id, -- идентификатор клиента при регистрации
        user_id -- идентификатор пользователя в приложении
    FROM registrations
) AS r ON r.client_id = w.client_id
LEFT JOIN
(
    SELECT DISTINCT user_id
    FROM payments -- платежи
) AS p ON p.user_id = r.user_id
GROUP BY date, channel, campaign
 

 

Такой отчет дает более точные данные об эффективности каждой кампании, но в силу своей когортности требует постоянного переизучения данных за прошлые дни. 

Заключение

Мы рассмотрели два подхода к построению отчета об эффективности рекламных кампаний. 

Соединение по кампании и дате действия — годится, если можно пренебречь разницей между датой клика и датой целевого действия. Хорош для оценки оперативной динамики ситуации, так как данные в отчете не изменяются задним числом.

Соединение по идентификатору клиента — подходит, если средний срок сделки сильно больше одного дня. Количество целевых действий может меняться «в прошлом», поэтому следует изучать и прошедшие периоды в пределах некоторого окна.

 

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники