Зарегистрируйтесь, чтобы продолжить обучение

Агрегация с помощью SUM и COUNT SQL: Оконные функции

В этом уроке изучим, как использовать SUM() и COUNT() для накопления данных, а также воспользуемся знаниями для помощи бизнесу.

Накопленный итог SUM

Оконная функция SUM() может использоваться для подсчета накопительного итога. Каждая запись будет содержать накопленный итог и за предыдущий период

SELECT
    s.id,
    s.sale_date,
    s.quantity,
    s.price,
    (s.quantity * s.price) AS amount,
    SUM(s.quantity * s.price) OVER (
        PARTITION BY s.sale_date
        ORDER BY
            s.id,
            s.sale_date,
            s.quantity
    ) AS amount_total
FROM
    sales AS s
ORDER BY s.sale_date;
id sale_date quantity price amount amount_total
1 2023-01-01T00:00:00.000Z 1 4 4 4
2 2023-01-01T00:00:00.000Z 2 2 4 8
3 2023-01-01T00:00:00.000Z 1 1 1 9
4 2023-01-01T00:00:00.000Z 1 2 2 11
5 2023-01-02T00:00:00.000Z 1 3 3 3
6 2023-01-02T00:00:00.000Z 1 3 3 6
7 2023-01-03T00:00:00.000Z 1 1 1 1
8 2023-01-03T00:00:00.000Z 1 3 3 4
9 2023-01-03T00:00:00.000Z 1 1 1 5
10 2023-01-03T00:00:00.000Z 1 1 1 6
11 2023-01-03T00:00:00.000Z 1 2 2 8

View on DB Fiddle

Накопительный итог COUNT

Рассмотрим запрос с оконной функцией COUNT() для подсчета общего количества записей для каждой даты продажи (sale_date) в таблице sales.

SELECT
    s.id,
    s.sale_date,
    quantity,
    COUNT(s.id) OVER (
        PARTITION BY s.sale_date
        ORDER BY
        s.sale_date,
        quantity
    ) AS total
FROM
    sales AS s
WHERE sale_date = '2023-01-01'
ORDER BY s.sale_date;
id sale_date quantity total
1 2023-01-01T00:00:00.000Z 1 3
3 2023-01-01T00:00:00.000Z 1 3
4 2023-01-01T00:00:00.000Z 1 3
2 2023-01-01T00:00:00.000Z 2 4

View on DB Fiddle

Давай разберем, как работает этот запрос:

  1. Сначала мы выбираем столбцы id, sale_date и quantity из таблицы sales.
  2. Мы также добавляем выражение COUNT(s.id) OVER(), чтобы посчитать общее количество записей для каждой даты продажи.
  3. PARTITION BY s.sale_date указывает, что мы хотим разделить данные на разные группы по sale_date.
  4. ORDER BY s.sale_date, quantity указывает порядок сортировки внутри каждой группы.
  5. Затем мы фильтруем только записи, где sale_date равен '2023-01-01'.
  6. И, наконец, мы сортируем результат по sale_date.

Результат запроса показывает, что для даты продажи '2023-01-01' у нас есть следующие записи:

  • id 1, quantity 1, total 3
  • id 3, quantity 1, total 3
  • id 4, quantity 1, total 3
  • id 2, quantity 2, total 4

Результат 3 в столбце total появляется несколько раз, потому что этот запрос использует функцию оконного агрегирования COUNT() с PARTITION BY по дате продажи. Это означает, что подсчет количества выполнится отдельно для каждой уникальной даты продажи.

Поскольку для всех строк с датой продажи '2023-01-01' используется одинаковое значение COUNT(s.id) (а именно 3), то это значение будет повторяться для каждой строки с этой датой, пока не изменится дата продажи. Когда появляется строка с датой продажи '2023-01-01' и другим количеством продаж, то COUNT(s.id) меняется на новое значение 4.

Результирующая таблица отображает итоговое количество продаж по каждой уникальной дате продажи, и поэтому для '2023-01-01' мы видим как 3, так и 4 в столбце total.

Поиск точки безубыточности

Предположим, что к нам приходят продуктовые аналитики и просят найти точку безубыточности. Это точка, где выручка от продаж покрывает все затраты на производство и приносит нулевую чистую прибыль. Здесь нам помогут функции с накопительным эффектом. Задача может звучать так - посчитать, когда был достигнут предел 1500 суммы покупок для каждого товара и вывести эту дату.

SELECT * FROM purchases;
id product_name purchase_date purchase_amount
1 Product A 2022-01-01T00:00:00.000Z 100
2 Product A 2022-01-02T00:00:00.000Z 200
3 Product A 2022-01-03T00:00:00.000Z 900
4 Product A 2022-01-04T00:00:00.000Z 900
5 Product B 2022-01-01T00:00:00.000Z 800
6 Product B 2022-01-02T00:00:00.000Z 900
7 Product B 2022-01-03T00:00:00.000Z 1000
8 Product C 2022-01-01T00:00:00.000Z 1100
9 Product C 2022-01-02T00:00:00.000Z 1200
10 Product C 2022-01-03T00:00:00.000Z 1300

View on DB Fiddle

Напишем запрос с поиском накопляемой суммы.

SELECT
    *,
    sum(purchase_amount)
        OVER (PARTITION BY product_name ORDER BY purchase_date, purchase_amount)
FROM purchases;
product_name purchase_date amount_sum
Product A 2022-01-04T00:00:00.000Z 2100
Product B 2022-01-02T00:00:00.000Z 1700
Product B 2022-01-03T00:00:00.000Z 2700
Product C 2022-01-02T00:00:00.000Z 2300
Product C 2022-01-03T00:00:00.000Z 3600

View on DB Fiddle

Для товара Product A точка безубыточности была достигнута четвертой продажей 2022-01-04. Но чтобы найти точку безубыточности для других товаров считать вручную дату неудобно. Воспользуемся фильтрацией и подзапросом.

WITH tab AS (
    SELECT
        *,
        SUM(purchase_amount)
            OVER (
                PARTITION BY product_name
                ORDER BY purchase_date, purchase_amount
            )
        AS amount_sum
    FROM purchases
)

SELECT
    product_name,
    purchase_date,
    amount_sum
FROM tab
WHERE amount_sum >= 1500;
product_name purchase_date amount_sum
Product A 2022-01-04T00:00:00.000Z 2100
Product B 2022-01-02T00:00:00.000Z 1700
Product B 2022-01-03T00:00:00.000Z 2700
Product C 2022-01-02T00:00:00.000Z 2300
Product C 2022-01-03T00:00:00.000Z 3600

View on DB Fiddle

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

WITH tab AS (
    SELECT
        *,
        SUM(purchase_amount)
            OVER (
                PARTITION BY product_name
                ORDER BY purchase_date, purchase_amount
            )
        AS amount_sum
    FROM purchases
),

rn_tab AS (
    SELECT
        product_name,
        purchase_date,
        amount_sum,
        ROW_NUMBER() OVER (PARTITION BY product_name ORDER BY amount_sum) AS rn
    FROM tab
    WHERE amount_sum >= 1500
)

SELECT
    product_name,
    purchase_date,
    amount_sum
FROM rn_tab
WHERE rn = 1;
product_name purchase_date amount_sum
Product A 2022-01-04T00:00:00.000Z 2100
Product B 2022-01-02T00:00:00.000Z 1700
Product C 2022-01-02T00:00:00.000Z 2300

View on DB Fiddle

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

Выводы

Агрегирующие оконные функции в SQL позволяют выполнять агрегатные операции над группами строк в пределах определенного окна. Оконные функции могут использоваться для вычисления накопительных сумм, средних значений, лидирующих значений и других агрегатов. Для определения окна используются ключевые слова PARTITION BY и ORDER BY, которые определяют границы окна.

Использование агрегирующих оконных функций в анализе данных может помочь бизнесу в различных задачах, например поиск точки безубыточности.

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff