Задачи на оконные функции SQL
Оконные функции·middle

Доля платежа в выручке клиента

Задача

Аналитик по удержанию разбирает структуру трат: «Возьми пару клиентов и по каждому их платежу посчитай, какую долю он занимает в общей сумме этого клиента. Хочу видеть, ровно человек платит или одним крупным чеком».

Верни id платежа, имя клиента, сумму платежа и его долю в процентах от всех трат этого клиента.

Схема данных

Учебная база проката dvdrental:

  • customer(customer_id, first_name, last_name, ...) — клиенты.
  • payment(payment_id, customer_id, amount, ...) — платежи; у одного клиента их несколько.

Решение

SELECT p.payment_id,
       c.first_name, c.last_name,
       p.amount,
       round(100.0 * p.amount
             / sum(p.amount) OVER (PARTITION BY p.customer_id), 2) AS pct_of_customer
FROM payment p
JOIN customer c USING (customer_id)
WHERE p.customer_id IN (1, 2)
ORDER BY p.customer_id, p.payment_id;

Разбор

Задача на «долю от целого», и оконный агрегат тут подходит идеально. Обычный SUM с GROUP BY схлопнул бы все платежи клиента в одну строку с итогом — и отдельных платежей мы бы уже не увидели. А SUM(p.amount) OVER (PARTITION BY p.customer_id) считает ту же сумму по клиенту, но не сворачивает строки: рядом с каждым платежом остаётся и он сам, и итог по его клиенту. Дальше делим одно на другое — получаем долю.

PARTITION BY p.customer_id — это раздел окна: сумма считается заново для каждого клиента, а не по всей таблице. У платежей клиента №1 в знаменателе будет его итог, у клиента №2 — его. Если бы PARTITION BY убрали, знаменателем стала бы выручка вообще всех, и доли получились бы микроскопические. Чем окно отличается от группировки — в вопросе что такое оконные функции.

Пара технических деталей. 100.0 * (а не 100 *) заставляет деление идти в числах с плавающей точкой — иначе целочисленное деление округлило бы всё до нуля. round(..., 2) оставляет два знака — проценты не нужно показывать с десятком цифр после запятой. Внутри окна ORDER BY не нужен: нам важна сумма по всему разделу, а не нарастающий итог, поэтому пишем голый PARTITION BY без сортировки.

WHERE p.customer_id IN (1, 2) сужает вывод до двух клиентов, чтобы результат было видно целиком и чтобы понять смысл — на границе между клиентами знаменатель меняется. Проверь сам: доли внутри каждого клиента складываются в 100%. Внешний ORDER BY p.customer_id, p.payment_id даёт стабильный порядок.

Если вместо доли нужен нарастающий итог по времени — смотри задачу накопительная выручка по дням.

Ожидаемый результат

payment_id | first_name | last_name | amount | pct_of_customer
--- | --- | --- | --- | ---
18495 | Mary | Smith | 5.99 | 5.22
18496 | Mary | Smith | 0.99 | 0.86
18497 | Mary | Smith | 9.99 | 8.71
18498 | Mary | Smith | 4.99 | 4.35
18499 | Mary | Smith | 4.99 | 4.35
18500 | Mary | Smith | 0.99 | 0.86
18501 | Mary | Smith | 3.99 | 3.48
22680 | Mary | Smith | 4.99 | 4.35
22681 | Mary | Smith | 3.99 | 3.48
22682 | Mary | Smith | 0.99 | 0.86
… всего строк: 56 (показаны первые 10)

Прорешай такие задачи сам

Читать разбор полезно, а настоящая прокачка — самому писать запросы и получать обратную связь. В симуляторе живая база и проверка запросов, лёгкий режим — бесплатно.

Прорешать в бесплатной песочнице

Смежные задачи

Вопросы по теме