В отчёте оконная функция указана без разбиения на группы. Как отсутствие PARTITION BY меняет область строк,...

В отчёте оконная функция указана без разбиения на группы. Как отсутствие PARTITION BY меняет область строк, доступную этой функции?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

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

Это отличается от GROUP BY: группировка сокращает число строк, тогда как оконная функция без разбиения не удаляет детализацию. Если требуется отдельный расчёт по каждому клиенту, региону или другому признаку, этот признак нужно явно указать в PARTITION BY.

Исторический контекст

Оконные функции появились как способ выполнять аналитические расчёты над связанным набором строк без потери исходных строк. Обычный GROUP BY хорошо решает задачу получения одной строки на группу, но неудобен, когда вместе с итогом нужно показать каждую операцию, заказ или событие.

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

Постановка проблемы

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

Если разработчик ожидает автоматического разбиения по клиентам, но не указывает PARTITION BY, сумма будет общей для всех строк. Отчёт может выглядеть правдоподобно, однако доли клиентов, регионов или подразделений окажутся рассчитаны относительно неправильного знаменателя.

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

Подробное решение

Оконная спецификация задаёт область строк, над которой работает функция. PARTITION BY делит входной набор на независимые секции; функция запускается отдельно внутри каждой секции. Если этой части нет, секция одна и включает все строки входного набора.

SELECT order_id, customer_id, amount, SUM(amount) OVER () AS total_amount, SUM(amount) OVER (PARTITION BY customer_id) AS customer_amount FROM orders;

В первом вычислении total_amount одинаков для всех строк и представляет сумму по всему набору orders, прошедшему предыдущие этапы запроса. Во втором вычислении сумма считается отдельно для каждого клиента, но строки заказов сохраняются.

Важно различать область секции и оконный кадр. PARTITION BY отвечает на вопрос, между какими группами строк расчёт изолируется, а ORDER BY и параметры кадра могут дополнительно ограничить строки, учитываемые для конкретной текущей строки. Например, накопительный итог может использовать всю секцию, но учитывать только строки от её начала до текущей.

Оконная функция работает не обязательно по всей исходной таблице. В её область попадают строки после применённых FROM, JOIN, WHERE, а при наличии группировки — после формирования групп и фильтрации через HAVING в соответствии с логической моделью обработки запроса. Поэтому фильтр в том же запросе может изменить знаменатель общей суммы.

GROUP BY и оконное вычисление дают разные формы результата. GROUP BY customer_id обычно создаёт одну строку на клиента, а SUM(amount) OVER (PARTITION BY customer_id) оставляет по строке на заказ и добавляет клиентский итог к каждой из них.

Ситуация из практики

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

Выбрали оконную сумму без PARTITION BY: она соответствует общей сумме уже отфильтрованного периода и повторяется в каждой строке. Для отдельной доли клиента добавили второе вычисление с PARTITION BY customer_id. Такой вариант одновременно сохранил платежи, дал общий знаменатель и явно выразил две разные аналитические области.

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

Что кандидаты часто упускают

1. Вопрос: Относится ли отсутствие PARTITION BY ко всей исходной таблице независимо от фильтров?

Ответ: Нет. Оконная функция видит не всю физическую таблицу, а набор строк на соответствующем этапе обработки запроса. Условие WHERE обычно исключает строки до оконного вычисления, поэтому SUM(amount) OVER () посчитает сумму по отфильтрованному периоду или набору статусов. Если нужно сравнить отфильтрованные строки с итогом по всей таблице, общий итог обычно вычисляют в отдельном уровне запроса до фильтрации либо используют другую архитектуру запроса.

2. Вопрос: Чем отсутствие PARTITION BY отличается от указания одного и того же константного значения в PARTITION BY?

Ответ: Практически оба варианта создают одну секцию для всех строк, если значение константы одинаково во всех строках. Однако отсутствие разбиения яснее выражает намерение получить глобальный расчёт и не требует вычисления дополнительного выражения. Если значение неожиданно перестанет быть константным, явное выражение начнёт делить строки на секции, поэтому оно может скрывать риск изменения семантики.

3. Вопрос: Можно ли получить глобальную сумму и сумму по клиенту одним оконным вычислением без изменения числа строк?

Ответ: Нет, одна оконная спецификация имеет одну область секции. Для глобальной суммы нужна секция без PARTITION BY, а для суммы по клиенту — секция с PARTITION BY customer_id; обычно это два отдельных оконных выражения. Они могут быть вычислены в одном операторе SELECT, и оба сохранят исходную детализацию, но будут иметь разные знаменатели и области расчёта.