Привет всем!

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

P.S. Если автор что-то не разобрал и не написал, значит он посчитал это не обязательным в рамках этой статьи))) 

Для примеров будем использовать небольшую таблицу, которая показывает оценки учеников по разным предметам. В БД табличка выглядит следующим образом

--создание таблицы
create table student_grades (
name varchar,
subject varchar,
grade int);

-- наполнение таблицы данными
insert into student_grades (
values
('Петя', 'русский', 4),
('Петя', 'физика', 5),
('Петя', 'история', 4),
('Маша', 'математика', 4),
('Маша', 'русский', 3),
('Маша', 'физика', 5),
('Маша', 'история', 3));

--запрос всех данных из таблицы
select * 
from student_grades;

SQL часто используется для вычислений в данных различных метрик или агрегаций значений по измерениям. Помимо функций агрегации для этого широко используются оконные функции. 

Оконная функция в SQL - функция, которая работает с выделенным набором строк (окном, партицией) и выполняет вычисление для этого набора строк в отдельном столбце. 

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

В чем заключается главное отличие оконных функций от функций агрегации с группировкой? 

При использовании агрегирующих функций предложение GROUP BY сокращает количество строк в запросе с помощью их группировки.

При использовании оконных функций количество строк в запросе не уменьшается по сравнении с исходной таблицей.

Порядок расчета оконных функций в SQL запросе

Сначала выполняется команда выборки таблиц, их объединения и возможные подзапросы под командой FROM.

Далее выполняются условия фильтрации WHERE, группировки GROUP BY и возможная фильтрация c HAVING

Только потом применяется команда выборки столбцов SELECT и расчет оконных функций под выборкой. 

После этого идет условие сортировки ORDER BY, где тоже можно указать столбец расчета оконной функции для сортировки. 

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

Синтаксис оконных функций

Синтаксис оконных функций вне зависимости от их класса будет так или иначе состоять из идентичных команд.

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

Классы Оконных функций

Множество оконных функций можно разделять на 3 класса:

  • Агрегирующие (Aggregate)

  • Ранжирующие (Ranking)

  • Функции смещения (Value)

Агрегирующие:

Можно применять любую из агрегирующих функций - SUM, AVG, COUNT, MIN, MAX

select name, subject, grade,
sum(grade) over (partition by name) as sum_grade,
avg(grade) over (partition by name) as avg_grade,
count(grade) over (partition by name) as count_grade,
min(grade) over (partition by name) as min_grade,
max(grade) over (partition by name) as max_grade
from student_grades;

Ранжирующие:

В ранжирующих функция под ключевым словом OVER обязательным идет указание условия ORDER BY, по которому будет происходить сортировка ранжирования. 

ROW_NUMBER() - функция вычисляет последовательность ранг (порядковый номер) строк внутри партиции, НЕЗАВИСИМО от того, есть ли в строках повторяющиеся значения или нет.

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

DENSE_RANK() - то же самое что и RANK, только в случае одинаковых значений DENSE_RANK не пропускает следующий числовой ранг, а идет последовательно.

select name, subject, grade,
row_number() over (partition by name order by grade desc),
rank() over (partition by name order by grade desc),
dense_rank() over (partition by name order by grade desc)
from student_grades;

Про NULL в случае ранжирования:

Для SQL пустые NULL значения будут определяться одинаковым рангом

Функции смещения:

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

LAG() - функция, возвращающая предыдущее значение столбца по порядку сортировки.

LEAD() - функция, возвращающая следующее значение столбца по порядку сортировки.

На простом примере видно, как можно в одной строке получить текущую оценку, предыдущую и следующую оценки Пети в четвертях.

--создание таблицы 
create table grades_quartal (
name varchar,
quartal varchar,
subject varchar,
grade int);

--наполнение таблицы данными
insert into grades_quartal (
values 
('Петя', '1 четверть', 'физика', 4),
('Петя', '2 четверть', 'физика', 3),
('Петя', '3 четверть', 'физика', 4),
('Петя', '4 четверть', 'физика', 5)
);

--запрос всех данных из таблицы
select *
from grades_quartal;
select name, quartal, subject, grade, 
lag(grade) over (order by quartal) as previous_grade,
lead(grade) over (order by quartal) as next_grade
from grades_quartal;

FIRST_VALUE()/LAST_VALUE() - функции возвращающие первое или последнее значение столбца в указанной партиции. В качестве аргумента указывает столбец, значение которого нужно вернуть. В оконной функции под словом OVER обязательное указание ORDER BY условия. 

В следующей версии статьи разберем отдельно такое понятие как фрейм окна функции или window frame и рассмотрим на простых примерах как он используется. 

Telegram канал про аналитику данных и бизнес-анализ

Комментарии (10)


  1. saipr
    02.05.2022 17:48
    +8

    все написано максимально простым языком для базового понимания.

    Если это для базового понимания, для начинающих, то я бы добавил несколько строк SQL, которые бы показали как была создана БД :


    image


    Матариал от этого только выиграет.


    1. daniil_dzheparov Автор
      03.05.2022 00:03
      +3

      спасибо за комментарий!
      прислушался и добавил в SQL запросы создание и наполнение таблиц данными


      1. saipr
        03.05.2022 08:24

        Всё сразу встало на свои места.


  1. Akina
    02.05.2022 22:13
    +4

    Оконная функция в SQL - функция, которая работает с выделенным набором строк (окном, партицией) и выполняет вычисление для этого набора строк в отдельном столбце. 

    Сей текст явно ставит знак равенства между окном и партицией. Что является явной ошибкой.

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

    Выражение партиции - никак не ограничено списком "по одному из столбцов или группе столбцов таблицы". Практически любое выражение - допустимо, единственное требование - скалярный результат выражения. Во всяком случае, я собственными глазами видел практический запрос, где выражение партиционирования включало коррелированный подзапрос. Вполне рабочий запрос.

    Синтаксис оконных функций вне зависимости от их класса будет так или иначе состоять из идентичных команд.

    FILTER clause поддерживается не в каждой СУБД.


    1. daniil_dzheparov Автор
      02.05.2022 23:58

      спасибо за комментарий!
      окно, партиция != фрейм окна (опять же правильно ли называть партицию окном - извечный вопрос определения, переведенного с иностранного языка), не стоит этим усложнять жизнь начинающим аналитикам - важно понять суть работу функций. про СУБД не уточнял где и что работает - опять же считаю это лишним условием в общем обзоре материала. "скалярный результат" и "коррелированный подзапрос", ну согласитесь же, что это звучит сильно сложно и непонятно для людей-новичков? Цель статьи - максимально упростить понимание и принцип работы, а не накидать множество условий и ограничений (это уже с опытом и насмотренностью придет, как и у вас)


      1. maxim_ge
        03.05.2022 01:44
        +2

        окно, партиция != фрейм окна (опять же правильно ли называть партицию окном — извечный вопрос определения, переведенного с иностранного языка)

        Интересно, а почему не использовать терминологию Microsoft?


        • Окно
        • Рамка окна
        • Секция
        • Секционирование?


      1. Akina
        03.05.2022 18:57
        -1

         "скалярный результат" и "коррелированный подзапрос", ну согласитесь же, что это звучит сильно сложно и непонятно для людей-новичков?

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

        А если не обладает - так он рано пришёл, пусть сперва букварь освоит.


  1. questor
    03.05.2022 14:33
    +1

    Статья хорошая, было бы неплохо продолжить и расписать более сложные случаи.


  1. RolexStrider
    03.05.2022 15:19

    Спасибо! Дополню от себя: есть немного нетипичное, но очень эффективное применение оконных функций: row_number() - очень помогает при поиске и удалении дубликатов в (плохо спроектированных) таблицах


    1. Akina
      03.05.2022 18:48

      очень помогает при поиске и удалении дубликатов

      Это как раз очевидное и тривиальное применение. Если этот вариант чем и интересен - так это правильными синтаксисом (ооо! написать правильный запрос для MySQL - вот где спотыкается половина начинающих!).

      Если уж задумываться о не самых очевидных применениях - то начинать надо с чего-нибудь вроде "gaps and islands".