Ответ
Оконные функции (Window Functions) в SQL выполняют вычисления над набором строк, связанных с текущей строкой, без свёртки результата в одну строку на группу (в отличие от GROUP BY). Они определяются с помощью предложения OVER().
Основные категории оконных функций:
-
Агрегатные функции как оконные:
SUM(),AVG(),COUNT(),MIN(),MAX(),STDDEV().- Позволяют вычислить агрегат в рамках «окна» (например, нарастающий итог).
SELECT employee_id, department_id, salary, AVG(salary) OVER(PARTITION BY department_id) AS avg_department_salary FROM employees;
-
Функции ранжирования:
ROW_NUMBER()— присваивает уникальный последовательный номер каждой строке в рамках окна.RANK()— присваивает ранг с пропусками при одинаковых значениях.DENSE_RANK()— присваивает ранг без пропусков при одинаковых значениях.NTILE(n)— разбивает строки наnпримерно равных групп (квантили).SELECT product_id, category, price, ROW_NUMBER() OVER(PARTITION BY category ORDER BY price DESC) as price_rank_in_category, RANK() OVER(ORDER BY price DESC) as global_price_rank_with_gaps FROM products;
-
Функции смещения (доступа к соседним строкам):
LAG(column, offset, default)— возвращает значение из строки, находящейся наoffsetстрок перед текущей.LEAD(column, offset, default)— возвращает значение из строки, находящейся наoffsetстрок после текущей.FIRST_VALUE(column)— возвращает первое значение в окне.LAST_VALUE(column)— возвращает последнее значение в окне.SELECT date, revenue, LAG(revenue, 1, 0) OVER(ORDER BY date) AS revenue_previous_day, revenue - LAG(revenue, 1, 0) OVER(ORDER BY date) AS daily_growth FROM sales;
-
Функции статистического распределения:
CUME_DIST()— относительный ранг строки (доля строк со значением <= текущему).PERCENT_RANK()— относительный ранг строки в диапазоне от 0 до 1.
Ключевые части предложения OVER():
PARTITION BY— разбивает данные на независимые окна/секции (аналогGROUP BY, но без свёртки).ORDER BY— определяет порядок строк внутри окна, что критично для нарастающих итогов, ранжирования и смещения.ROWS/RANGE BETWEEN— определяет границы окна относительно текущей строки (например,ROWS BETWEEN 2 PRECEDING AND CURRENT ROWдля скользящего среднего по 3 строкам).
Ответ 18+ 🔞
Да ты посмотри, какие они хитрожопые, эти оконные функции! Это ж просто пиздец, как удобно, когда тебе надо посчитать что-то по группе, но при этом все строки на месте остаются, не сворачиваются в одну, как с обычным GROUP BY. Представь: ты видишь каждую зарплату, а рядом — среднюю по отделу. Красота, ёпта!
Вот смотри, основные банды, про которые надо знать:
-
Агрегатчики, которые стали оконными. Это наши старые знакомые:
SUM(),AVG(),COUNT(). Только теперь они не давят всё в одну кашу, а считают своё дело в рамках «окошка». Например, нарастающий итог — это же просто песня, а не функция!SELECT employee_id, department_id, salary, AVG(salary) OVER(PARTITION BY department_id) AS avg_department_salary FROM employees;Смотри, что тут происходит: для каждого бедолаги-сотрудника мы показываем его зарплату, а рядом — среднюю по его же отделу. И никто никого не сгруппировал в кучку! Каждая строка жива-здорова.
-
Ранжировщики — настоящие заводилы. Вот тут начинается веселье. Нумеруют всех подряд, как на перекличке.
ROW_NUMBER()— ну, тут всё ясно, выдаёт порядковый номер: раз, два, три. Уникальный, без дублей.RANK()— а этот чувак, если значения одинаковые, ставит им один ранг, а потом делает прыжок. Было два первых места? Следующий будет третий. Честно, но с пропусками.DENSE_RANK()— а этот добряк пропусков не любит. Два первых места? Следующий всё равно будет второй. Все довольны.NTILE(n)— этот делит всех наnкорзинок. Типа, «разбей мне всех сотрудников на 4 группы по зарплате». Получатся квантильчики.SELECT product_id, category, price, ROW_NUMBER() OVER(PARTITION BY category ORDER BY price DESC) as price_rank_in_category, RANK() OVER(ORDER BY price DESC) as global_price_rank_with_gaps FROM products;
-
Соседи-подсматриватели. Мои любимчики! Позволяют заглянуть к соседней строке, как через забор.
LAG(column, offset, default)— оглядывается назад, на указанное количество строк. «А что там вчера было?»LEAD(column, offset, default)— смотрит вперёд. «А что там завтра будет?»FIRST_VALUE(column)иLAST_VALUE(column)— хватают первое и последнее значение в окне. Без них никуда.SELECT date, revenue, LAG(revenue, 1, 0) OVER(ORDER BY date) AS revenue_previous_day, revenue - LAG(revenue, 1, 0) OVER(ORDER BY date) AS daily_growth FROM sales;Видишь? Мы взяли выручку за вчера (
LAG), вычли её из сегодняшней и получили прирост. Элегантно, блядь, до безобразия!
-
Статистические зануды.
CUME_DIST()иPERCENT_RANK(). Ну, эти нужны, когда тебе надо не просто ранжировать, а понять, в каком процентиле находится строка. Полезно, но мозг иногда вывихивают.
А теперь, ёпта, самое главное — как этим всем рулить? Всё решает волшебная штука OVER().
В ней три основных рычага:
PARTITION BY— это какGROUP BY, только для окон. Разбивает таблицу на независимые куски (отделы, категории, годы). В каждом таком куске своё окошко и свои вычисления.ORDER BY— а это порядок внутри окошка. Критически важен! Без него нарастающий итог не посчитать, иLAGсLEADбудут себя странно вести. Это как сказать: «Стройся по зарплате, от большей к меньшей, и теперь давай считай».ROWS/RANGE BETWEEN— вот это уже высший пилотаж. Тут ты указываешь границы окна. Например, «смотри на две строки перед текущей и на одну после». Идеально для скользящего среднего! Если не указать, по умолчанию окно может вести себя неочевидно, особенно сLAST_VALUE()— так что тут надо глаза держать открытыми.
Короче, инструмент, блядь, мощнейший. Сначала голова болит, а когда врубишься — без него жить не можешь. Как будто тебе всю жизнь давали только молоток, а тут подсунули целый швейцарский нож с кучей лезвий.