
Оконные функции SQL позволяют выполнять расчёты по наборам строк без группировки, сохраняя структуру исходной таблицы. Например, ROW_NUMBER(), RANK() и DENSE_RANK() дают возможность ранжировать записи внутри заданных категорий, что особенно полезно при анализе продаж по регионам или при сегментации клиентов по активности.
SUM() OVER и AVG() OVER позволяют вычислять накопительные суммы и скользящие средние, что ускоряет построение временных рядов и прогнозирование показателей. При работе с транзакционными данными можно сразу получать показатели кумулятивной выручки или среднего чека без дополнительных подзапросов.
Использование оконных функций в сочетании с PARTITION BY и ORDER BY оптимизирует анализ сложных наборов данных. Например, можно рассчитать долю каждого продукта в месячной выручке по категории или определить отклонения от среднего по региону в один запрос, снижая нагрузку на базу данных.
Для аналитиков важно понимать, что оконные функции эффективны при работе с большими таблицами, где классические GROUP BY или подзапросы создают избыточные джойны. Включение LEAD() и LAG() позволяет сравнивать текущие значения с предыдущими строками, что ускоряет выявление трендов и аномалий в поведении пользователей или динамике продаж.
Сравнение строк в пределах группы с помощью ROW_NUMBER()
Функция ROW_NUMBER() позволяет присвоить уникальный порядковый номер каждой строке внутри группы, определяемой через PARTITION BY. Это упрощает анализ последовательностей и выделение лидеров в группе. Например, для анализа продаж по регионам и выявления топ-продуктов за месяц:
SELECT region, product, sales, ROW_NUMBER() OVER(PARTITION BY region ORDER BY sales DESC) AS rn FROM sales_data;
В этом примере ROW_NUMBER() присвоит 1 строке с наибольшими продажами в каждом регионе. Чтобы сравнить текущую запись с предыдущей по продажам, можно использовать CTE:
WITH ranked_sales AS (SELECT region, product, sales, ROW_NUMBER() OVER(PARTITION BY region ORDER BY sales DESC) AS rn FROM sales_data) SELECT r1.region, r1.product, r1.sales AS current_sales, r2.sales AS previous_sales FROM ranked_sales r1 LEFT JOIN ranked_sales r2 ON r1.region = r2.region AND r1.rn = r2.rn + 1;
Такой подход позволяет вычислять разницу между текущей и предыдущей строкой без использования подзапросов с агрегатами. Для больших наборов данных рекомендуется индексировать колонки, участвующие в PARTITION BY и ORDER BY, чтобы ускорить сортировку внутри окон.
ROW_NUMBER() особенно эффективен при фильтрации уникальных записей в пределах группы. Например, оставляя только первую позицию по продажам: SELECT * FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY region ORDER BY sales DESC) AS rn FROM sales_data) t WHERE rn = 1;
Применение ROW_NUMBER() позволяет строить динамические рейтинги, выявлять аномалии и создавать последовательности сравнения строк без дублирования данных, что делает аналитику более прозрачной и управляемой.
Агрегация данных с сохранением детализации через SUM() OVER()

Функция SUM() OVER() позволяет вычислять суммарные значения в пределах определенного окна строк, сохраняя при этом исходные детали каждой записи. Это особенно полезно для анализа транзакций, когда требуется видеть как индивидуальные показатели, так и общие результаты.
Например, в таблице sales с колонками region, month и revenue можно рассчитать суммарный доход по региону без потери данных о каждом месяце:
Пример SQL-запроса:
SELECT region, month, revenue, SUM(revenue) OVER(PARTITION BY region) AS total_region_revenue FROM sales;
В этом запросе PARTITION BY region формирует окно по каждому региону, а SUM(revenue) OVER() вычисляет общий доход региона. Результат позволяет сравнивать месячные показатели с суммарным значением, выявлять отклонения и аномалии без дополнительных объединений таблиц.
Для сложного анализа можно использовать ORDER BY внутри окна, чтобы получать кумулятивные суммы:
SELECT region, month, revenue, SUM(revenue) OVER(PARTITION BY region ORDER BY month) AS cumulative_revenue FROM sales;
Такой подход позволяет строить тренды и графики накопления дохода по месяцам, сохраняя детальную информацию о каждой строке. Рекомендация: всегда определяйте PARTITION BY и при необходимости ORDER BY, чтобы результаты были точными и соответствовали аналитическим целям.
Использование SUM() OVER() снижает количество JOIN и подзапросов, упрощает отчеты и ускоряет обработку больших объемов данных, особенно при работе с OLAP-системами и финансовыми отчетами.
Определение рангов и позиций с RANK() и DENSE_RANK()

Функции RANK() и DENSE_RANK() назначают числовой ранг строкам в пределах заданного окна, определяемого оператором OVER(). RANK() присваивает одинаковый ранг строкам с равными значениями, но пропускает следующие номера. DENSE_RANK() также учитывает равенство, но без пропусков в нумерации.
Пример использования RANK():
SELECT employee_id, salary, RANK() OVER (ORDER BY salary DESC) AS salary_rank FROM employees;
В этом запросе сотрудникам с одинаковой зарплатой присваивается один и тот же ранг, а следующий ранг пропускается. Если две строки занимают 1-е место, следующая будет 3-й.
Пример DENSE_RANK():
SELECT employee_id, salary, DENSE_RANK() OVER (ORDER BY salary DESC) AS salary_dense_rank FROM employees;
Здесь одинаковая зарплата получает одинаковый ранг, но следующий ранг идет без пропусков: две строки с 1-м рангом, следующая – 2-я.
Для аналитики продаж или оценки эффективности сотрудников RANK() полезен, когда важно учитывать позиции с учетом пропусков, например, при формировании топ-10 лидеров. DENSE_RANK() предпочтителен для подсчета плотных рангов без «провалов» в нумерации.
Рекомендации по применению:
- Использовать PARTITION BY для разделения данных по категориям, например, отделам или регионам.
- Совмещать с ORDER BY для точного определения критериев ранжирования.
- Применять в подзапросах или CTE для последующей фильтрации топ-N элементов.
- Оценивать производительность на больших таблицах, так как оконные функции вычисляют ранг для всех строк окна.
Скользящие средние и накопительные итоги с использованием AVG() OVER()

Функция AVG() в сочетании с оконной функцией OVER() позволяет рассчитывать динамические средние значения без агрегации всей таблицы. Например, для анализа ежедневных продаж можно вычислить 7-дневное скользящее среднее, учитывая текущий день и шесть предыдущих:
SELECT order_date, sales, AVG(sales) OVER(ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS moving_avg FROM sales_data;
Использование конструкции ROWS BETWEEN обеспечивает точный контроль диапазона, включая только нужное количество строк, в отличие от RANGE, который учитывает значения, а не позиции строк. Для накопительного итога достаточно ограничиться текущей строкой и всеми предыдущими:
SELECT order_date, sales, AVG(sales) OVER(ORDER BY order_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_avg FROM sales_data;
При работе с пропущенными датами рекомендуется применять LEFT JOIN с календарной таблицей, чтобы скользящее среднее отражало отсутствие данных, а не искажало результат. Для больших таблиц оптимально добавлять PARTITION BY, разделяя данные по категориям, например, по регионам или продуктовым линейкам:
AVG(sales) OVER(PARTITION BY region ORDER BY order_date ROWS BETWEEN 6 PRECEDING AND CURRENT ROW)
Эти техники позволяют получать оперативные показатели тренда и накопительные средние без создания промежуточных таблиц и сложных подзапросов. В аналитике продаж и финансов скользящие средние помогают выявлять сезонные колебания, а накопительные итоги – отслеживать прогресс выполнения планов в реальном времени.
Вычисление разниц между строками через LAG() и LEAD()

Функции LAG() и LEAD() позволяют обращаться к значениям предыдущей или следующей строки в пределах одного окна, что делает их незаменимыми для вычисления разниц между строками без использования сложных JOIN.
Примеры применения:
- Анализ изменения продаж по дням.
- Вычисление разницы в показателях производительности между периодами.
- Определение отклонений в временных рядах.
Синтаксис функций:
LAG(column, offset, default) OVER (PARTITION BY ... ORDER BY ...)– возвращает значение из предыдущей строки с указанным смещением.LEAD(column, offset, default) OVER (PARTITION BY ... ORDER BY ...)– возвращает значение из следующей строки с указанным смещением.
Пример вычисления разницы продаж по дням:
SELECT
sale_date,
amount,
amount - LAG(amount) OVER (ORDER BY sale_date) AS diff_from_previous
FROM sales;
Рекомендации по использованию:
- Всегда явно указывайте
ORDER BY, иначе результат будет непредсказуемым. - Используйте
PARTITION BYдля разделения данных по категориям или регионам. - Для заполнения пропусков применяйте аргумент
default, чтобы избежать NULL при первой или последней строке. - Для нескольких периодов одновременно используйте разные значения
offsetв LAG/LEAD.
Пример с категорией и несколькими смещениями:
SELECT
category,
sale_date,
amount,
amount - LAG(amount, 1, 0) OVER (PARTITION BY category ORDER BY sale_date) AS diff_1_day,
amount - LAG(amount, 7, 0) OVER (PARTITION BY category ORDER BY sale_date) AS diff_7_days
FROM sales;
Такой подход позволяет строить детализированные аналитические отчеты, выявлять тренды и аномалии без дополнительных таблиц или сложных объединений.
Фильтрация данных внутри окон с PARTITION BY и ORDER BY
Оконные функции позволяют анализировать данные не только по всей таблице, но и внутри логически связанных групп с помощью PARTITION BY. Например, при расчете накопительной суммы по заказам клиентов важно разделять данные по каждому клиенту, чтобы результат был корректным для каждой группы.
Сочетание PARTITION BY с ORDER BY внутри окна задает порядок обработки строк, что критично для функций ROW_NUMBER(), RANK(), SUM() OVER(). Например, выражение:
SUM(amount) OVER (PARTITION BY customer_id ORDER BY order_date)
вычисляет накопительную сумму по каждому клиенту в порядке даты заказа. Любые попытки фильтровать данные после агрегирования с помощью WHERE приведут к потере контекста окна. Вместо этого фильтрацию лучше выполнять через CASE внутри функции:
SUM(CASE WHEN status = 'completed' THEN amount ELSE 0 END) OVER (PARTITION BY customer_id ORDER BY order_date)
Это позволяет учитывать только завершенные заказы без разбиения логики на несколько подзапросов.
Для анализа топ-N значений по группе используют ROW_NUMBER() OVER (PARTITION BY … ORDER BY …). Например, чтобы получить первые три заказа каждого клиента по сумме:
SELECT * FROM (SELECT *, ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rn FROM orders) t WHERE rn <= 3
Важно помнить, что ORDER BY внутри окна не сортирует итоговый результат всей таблицы – он лишь задает контекст для функции. Для визуализации или экспорта требуется отдельное ORDER BY на уровне SELECT.
Рекомендация: всегда четко определяйте границы PARTITION BY и порядок ORDER BY для точного фильтрования и последовательного расчета показателей внутри групп. Это снижает риск ошибок при построении отчетов и повышает читаемость запросов.
Использование оконных функций для анализа трендов и аномалий
Оконные функции в SQL позволяют анализировать данные в разрезе временных интервалов и групп, не объединяя строки в агрегации. Для выявления трендов и аномалий чаще всего применяются функции ROW_NUMBER(), LAG(), LEAD(), AVG() и STDDEV() в сочетании с OVER(PARTITION BY ... ORDER BY ...).
Примеры использования:
- Выявление роста или падения показателей:
LAG(value) OVER(ORDER BY date)позволяет сравнивать текущую и предыдущую запись, вычислять разницу и определять тренд. - Сглаживание временных рядов:
AVG(value) OVER(PARTITION BY category ORDER BY date ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)формирует скользящее среднее для устранения случайных колебаний. - Выявление аномалий: Разница между текущим значением и скользящим средним, деленная на стандартное отклонение
STDDEV(value) OVER(PARTITION BY category ORDER BY date ROWS BETWEEN 3 PRECEDING AND CURRENT ROW), позволяет обнаруживать значения, выходящие за пределы нормального диапазона. - Ранжирование элементов по отклонению:
ROW_NUMBER() OVER(PARTITION BY category ORDER BY abs(value - moving_avg) DESC)позволяет определить наибольшие выбросы.
Рекомендации для практического анализа:
- Использовать
PARTITION BYдля разделения данных по логическим группам (например, по регионам или категориям продуктов). - Определять размер окна (
ROWS BETWEEN X PRECEDING AND CURRENT ROW) в зависимости от длины временного ряда и сезонности данных. - Сравнивать текущие значения с прошлым периодом и с локальными средними, чтобы выявить как краткосрочные, так и долгосрочные отклонения.
- Для аномалий учитывать не только абсолютное отклонение, но и стандартное отклонение, чтобы исключить ложные срабатывания при высоком разбросе данных.
- Комбинировать оконные функции с фильтрацией и условной логикой (
CASE WHEN), чтобы автоматизировать обнаружение аномалий в отчетах.
Применение этих методов позволяет получать точные сигналы о тенденциях и нестандартных событиях, минимизируя необходимость сложной предобработки данных вне SQL.
Сравнение показателей между разными группами с оконными функциями

Оконные функции SQL позволяют вычислять показатели в рамках групп без необходимости агрегации всей таблицы. Для сравнения, например, продаж по регионам, можно использовать функцию RANK() или ROW_NUMBER(), чтобы определить позиции каждого филиала относительно других в своей группе.
Пример: подсчет среднего чека по отделам с использованием AVG() OVER(PARTITION BY department_id):
| employee_id | department_id | sale_amount | avg_sale_by_department |
|---|---|---|---|
| 101 | 1 | 1200 | 1150 |
| 102 | 1 | 1100 | 1150 |
| 103 | 2 | 900 | 950 |
| 104 | 2 | 1000 | 950 |
Для анализа динамики можно применять LAG() и LEAD(), чтобы сравнивать показатели текущего периода с предыдущим или следующим. Это позволяет выявить рост или падение внутри групп, не соединяя таблицу с самой собой.
Рекомендации:
- Используйте
PARTITION BYдля определения группы, внутри которой будут вычисляться показатели. - Добавляйте
ORDER BYвнутри оконной функции для правильного расчета рангов или временных изменений. - Применяйте несколько оконных функций одновременно для комплексного анализа:
SUM() OVER()+RANK() OVER()для оценки доли в группе и позиции.
Пример комбинированного расчета суммы продаж и ранга внутри региона:
| region | store_id | total_sales | sales_rank |
|---|---|---|---|
| East | 201 | 15000 | 1 |
| East | 202 | 12000 | 2 |
| West | 301 | 18000 | 1 |
| West | 302 | 16000 | 2 |
Использование оконных функций сокращает количество подзапросов и позволяет получать показатели по группам в одном запросе, сохраняя детализированные данные и облегчая сравнение между сегментами.
Вопрос-ответ:
Что такое оконные функции в SQL и чем они отличаются от агрегатных функций?
Оконные функции позволяют выполнять вычисления по набору строк, определяемому с помощью оконного фрейма, при этом каждая строка сохраняет свою уникальность в результирующем наборе данных. В отличие от стандартных агрегатных функций, которые сводят строки к одному значению (например, SUM или AVG), оконные функции могут возвращать значения для каждой строки, учитывая соседние строки в пределах заданного окна.
Какие задачи в аналитике проще решать с помощью оконных функций?
С их помощью удобно рассчитывать накопительные суммы, скользящие средние, ранжировать строки, определять долю строки в общем объеме, находить предыдущие или следующие значения в последовательности. Например, можно легко посчитать средний доход сотрудников по отделу за последние месяцы или определить позиции товаров по продажам без создания сложных подзапросов.
В чем разница между ROW_NUMBER(), RANK() и DENSE_RANK()?
ROW_NUMBER() присваивает уникальный номер каждой строке в пределах окна, даже если значения повторяются. RANK() присваивает одинаковый ранг строкам с одинаковыми значениями, но при этом пропускает следующие позиции, создавая «разрывы» в последовательности. DENSE_RANK() работает похоже на RANK(), но пропусков в нумерации не делает — ранги идут подряд. Выбор функции зависит от того, как важно учитывать повторяющиеся значения при ранжировании.
Можно ли использовать оконные функции вместе с WHERE и GROUP BY?
Прямое использование оконных функций в предложении WHERE невозможно, так как они вычисляются после фильтрации строк. Для фильтрации по результатам оконных функций обычно применяют подзапросы или CTE. При GROUP BY агрегатные функции группируют данные, а оконные функции могут работать поверх этих групп, создавая дополнительный слой аналитики без изменения структуры групп.
Как влияет определение окна (PARTITION BY и ORDER BY) на результат функций?
PARTITION BY разделяет набор данных на подгруппы, внутри которых вычисляется функция, а ORDER BY определяет порядок строк для вычисления накопительных или ранговых значений. Например, при расчете ранга сотрудников по отделам PARTITION BY отдел будет группировать сотрудников по отделам, а ORDER BY зарплата определит порядок ранжирования внутри каждого отдела. Изменение этих параметров полностью меняет результат вычислений.
