Собесов

Ренессанс Кредит SQL — помесячная статистика коммуникаций одним запросом

SQLАгрегацияСредняяMiddle

Условие

По таблице коммуникаций comms(ow_id, sent_date, channel_nm, partner_id, sent, delivered, opened, clicked, cell_pk) собрать в одном запросе по месяцам:

  • кол-во отправленных / доставленных / открытых / с переходом коммуникаций;
  • среднее число отправленных коммуникаций на клиента;
  • среднее число партнёров, по которым клиент получил коммуникацию;
  • среднее число каналов, по которым клиент получил коммуникацию.

Решение

Подход

Разбиваем на два уровня агрегации: сначала на уровне (месяц, клиент) считаем количества и distinct’ы, потом на уровне месяца берём суммы и средние.

Реализация

WITH per_client_month AS (
  SELECT
    DATE_TRUNC('month', sent_date) AS m,
    ow_id,
    SUM(sent)                          AS sent_cnt,
    SUM(delivered)                     AS delivered_cnt,
    SUM(opened)                        AS opened_cnt,
    SUM(clicked)                       AS clicked_cnt,
    COUNT(DISTINCT partner_id)         AS partners_cnt,
    COUNT(DISTINCT channel_nm)         AS channels_cnt
  FROM comms
  GROUP BY DATE_TRUNC('month', sent_date), ow_id
)
SELECT
  m AS month,
  SUM(sent_cnt)            AS sent_total,
  SUM(delivered_cnt)       AS delivered_total,
  SUM(opened_cnt)          AS opened_total,
  SUM(clicked_cnt)         AS clicked_total,
  AVG(sent_cnt)::numeric(10,2)     AS avg_sent_per_client,
  AVG(partners_cnt)::numeric(10,2) AS avg_partners_per_client,
  AVG(channels_cnt)::numeric(10,2) AS avg_channels_per_client
FROM per_client_month
GROUP BY m
ORDER BY m;

Альтернатива одним уровнем (без CTE)

Можно избежать вложенного агрегирования через COUNT(DISTINCT) FILTER и условные суммы, но среднее «на клиента» всё равно требует первого уровня группировки по ow_id. CTE прозрачнее.

Подводные камни

  1. Среднее на отправленную коммуникацию vs на клиента. «Среднее количество отправленных на клиента» = total_sent / unique_clients_who_got_anything. Если посчитать AVG(sent) напрямую по таблице — получим среднее на строку (на коммуникацию), а это бессмысленная метрика.
  2. COUNT(DISTINCT) дороже, чем COUNT. На большом объёме лучше предагрегировать в CTE.
  3. Клиенты, которым ничего не отправили в месяце, в среднее не попадают. Если задача предполагает «среднее по всей аудитории», нужен LEFT JOIN со справочником клиентов и COALESCE(... , 0).
  4. Дубли коммуникаций. Если cell_pk уникален — ОК. Если в таблице бывают дубли — SUM(sent) посчитает их повторно. Возможна предагрегация по cell_pk.
  5. Часовой пояс / сдвиг месяца. DATE_TRUNC('month', sent_date) зависит от локали БД.

Эталонный ответ

Двухуровневая агрегация: сначала per (месяц, клиент) с SUM и COUNT(DISTINCT), потом per месяц с SUM и AVG. Все требуемые метрики в одном итоговом SELECT.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти