Что такое оконные функции в SQL?

Ответ

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

Ключевые компоненты:

  • OVER(): Обязательная конструкция, определяющая "окно" — набор строк для вычисления.
  • PARTITION BY: Разбивает данные на группы (партиции), внутри которых работает функция. Аналогично GROUP BY, но без агрегации.
  • ORDER BY: Задает порядок строк внутри окна или партиции, что критично для функций ранжирования и накопительных итогов.
  • ROWS/RANGE BETWEEN: Определяет фрейм — точный поднабор строк внутри окна для расчета (например, ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING).

Основные типы оконных функций:

  1. Ранжирующие: ROW_NUMBER(), RANK(), DENSE_RANK(), NTILE().
  2. Агрегатные: SUM(), AVG(), COUNT(), MIN(), MAX() (используемые с OVER).
  3. Функции смещения: LAG() (значение из предыдущей строки), LEAD() (значение из следующей строки).
  4. Функции доступа к значениям: FIRST_VALUE(), LAST_VALUE(), NTH_VALUE().

Практический пример — расчет накопительного итога и ранга:

SELECT
    employee_id,
    department_id,
    salary,
    -- Накопительная сумма зарплат по отделам
    SUM(salary) OVER (
        PARTITION BY department_id
        ORDER BY hire_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total,
    -- Ранг зарплаты внутри отдела
    RANK() OVER (
        PARTITION BY department_id
        ORDER BY salary DESC
    ) AS salary_rank_in_dept
FROM employees;

Это мощный инструмент для сложных аналитических запросов без необходимости использования коррелированных подзапросов.

Ответ 18+ 🔞

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

Представь, у тебя таблица с зарплатами. Хочешь узнать, кто в отделе самый жирный, но при этом видеть всех подряд, а не только топ-1? Вот тут они и выручают, эти оконные функции. Ёпта, это как смотреть на всю толпу и сразу видеть, у кого самая дорогая тачка, но при этом всех остальных тоже видно.

Из чего это говно собрано:

  • OVER(): Это, сука, основа основ. Без этого — нихуя не работает. Внутри него ты и определяешь, над каким именно «окошком» данных будет колдовать функция.
  • PARTITION BY: Делит твои данные на группы. Скажем, по отделам. Функция будет работать внутри каждой группы отдельно. Это как GROUP BY, только строки не схлопываются — красота!
  • ORDER BY: Задает порядок. Без него некоторые функции будут работать как попало, а другие — вообще нихуя не делать. Особенно важно для подсчета всяких накопительных сумм.
  • ROWS/RANGE BETWEEN: Это уже тонкая настройка, хитрая жопа. Тут ты говоришь: «Считай не по всем строкам партиции, а, например, только по предыдущей и следующей от текущей». Мощно, но сначала голова болеть будет.

Какие бывают эти функции, ёпта:

  1. Ранжирующие: ROW_NUMBER(), RANK(), DENSE_RANK(). Выдают места: первый, второй, третий... Только вот RANK() может дать одинаковые места, а потом прыгнуть (1, 2, 2, 4), а DENSE_RANK() — нет (1, 2, 2, 3). ROW_NUMBER() всегда уникальный, даже если значения одинаковые. Волнение ебать, когда выбираешь не ту.
  2. Агрегатные: Те же самые SUM(), AVG(), но с прицепом OVER(). Они не схлопывают строки, а считают агрегат для каждой строки в её окне. Овердохуища возможностей открывается.
  3. Функции смещения: LAG() и LEAD(). Ни хуя себе, полезные штуки. LAG смотрит на предыдущую строку (в заданном порядке), LEAD — на следующую. Идеально для сравнения «сегодня vs вчера» в одном запросе.
  4. Функции доступа: FIRST_VALUE(), LAST_VALUE(). Берут первое или последнее значение в окне. С LAST_VALUE() надо аккуратнее, по умолчанию её окно может вести себя неочевидно.

Смотри, как это выглядит в деле. Вот тебе реальный пример, чтобы не быть голословным:

SELECT
    employee_id,
    department_id,
    salary,
    -- Накопительная сумма зарплат по отделам
    SUM(salary) OVER (
        PARTITION BY department_id
        ORDER BY hire_date
        ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
    ) AS running_total,
    -- Ранг зарплаты внутри отдела
    RANK() OVER (
        PARTITION BY department_id
        ORDER BY salary DESC
    ) AS salary_rank_in_dept
FROM employees;

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

Короче, инструмент — ядрёна вошь. Сначала кажется сложным, но как врубишься — обратной дороги нет. Просто э, бошка, думай, когда окна настраиваешь.