Оконные функции SQL

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

Принцип работы

Возможно, вы зададитесь вопросом: «Что значит оконные?».

В стандартном SQL-запросе все наборы строк рассматриваются как один сплошной блок данных, для которого и вычисляются агрегатные значения.

Однако, когда применяются оконные функции, запрос сегментируется на группы строк (или «окна»), и для каждого такого сегмента подсчитываются индивидуальные агрегатные значения.

Это окно, которое подаётся в оконную функцию, может быть:

  • всей таблицей
  • отдельными партициями таблицы, то есть группой строк на основе одного или нескольких полей
  • или даже конкретным диапазоном строк в пределах таблицы или партиции. Например, мы можем определить окно, которое будет передаваться в оконную функцию, как предыдущая + текущая строка таблицы. И тогда для каждой строки значение агрегатной функции будет подсчитываться по-своему, так как данные, которые поступают в функцию будут динамически меняться от строке к строке. Окно будет как бы «скользить» по таблице.

Визуализация

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

Давайте рассмотрим как это может выглядеть. Для этого возьмём оконную функцию AVG для вычисления среднего значения и вот такую небольшую таблицу:

Изначальная таблица

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

  • Если в качестве окна указать всю таблицу, то для всех строк окно будет совпадать и на вход функции AVG будет поступать один и тот же набор данных, и, соответственно, результат будет одинаковый.

    Схема разбиения на партиции

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

    Схема разбиения на партиции

  • В качестве окна можно указать и более специфический набор строк. Например, окно можно определить как "предыдущая + текущая строка" таблицы. Тогда это будет выглядеть следующим образом:

    Схема разбиения на партиции

    Стоит отметить, что для первой строки окно будет состоять только из 1-ой записи, так как предыдущей строки нет.

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

MySQL
SELECT <оконная_функция>(<поле_таблицы>)
OVER (
      [PARTITION BY <столбцы_для_разделения>]
      [ORDER BY <столбцы_для_сортировки>]
      [ROWS|RANGE <определение_диапазона_строк>]
)

Где:

  • <оконная_функция>(<поле_таблицы>) — используемая оконная функция. Например AVG(price).
  • Далее следует OVER, который определяет окно (группу строк), которое будет передаваться в оконную функцию. Если конструкцию OVER () оставить без параметров, то окном будет выступать вся таблица.

Далее внутри OVER следуют 3 необязательных параметра, с помощью которых можно гибко настраивать окно:

  • с помощью PARTITION BY <столбцы_для_разделения> выборка делится на непересекающиеся подмножества, где каждое подмножество содержит строки с одинаковыми значениями в одном или нескольких столбцах, образуются партиции.
  • с помощью ORDER BY <столбцы_для_сортировки> устанавливается порядок строк внутри окна, особо важную роль играет в оконных функциях ранжирования.
  • с помощью ROWS|RANGE <определение_диапазона_строк> формируются диапазоны строк. С помощью этого параметра можно указать сколько строк брать до и после текущей в окно.

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

Пример использования оконной функции

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

Для начала давайте получим список студентов и идентификатор класса, в котором они учатся:

MySQL
SELECT
    Student.first_name,
    Student.last_name,
    Student_in_class.class
FROM
    Student_in_class
JOIN
    Student ON Student_in_class.student = Student.id;
first_namelast_nameclass
NikolajSokolov9
VyacheslavEliseev9
IvanEfremov9
AnatolijZHdanov9
GeorgijNoskov9
ArtyomSergeev9
ArinaEvseeva9
AngelinaVoroncova9
EkaterinaUstinova9
RaisaLapina9
LeonidIgnatov9
SnezhanaSeliverstova9
SemyonBiryukov9
GeorgijBaranov8
YUliyaVishnyakova8
ValentinaBolshakova8
LeonidKryukov8
VladislavCvetkov8
SnezhanaMorozova8
LyubovBorisova8
AnfisaKalashnikova8
AnnaOsipova8
KristinaMyasnikova8
KristinaSmirnova8
BorisSimonov7
DmitrijTrofimov7
YAkovRozhkov7
FyodorDrozdov7
GlebStrelkov7
AngelinaLukina7
NinaOdincova7
ValeriyaNovikova7
GrigorijKapustin7
VitalijPanfilov7
SvyatoslavTarasov6
MatvejYAkushev6
IlyaAlekseev6
LyubovZaharova6
PolinaSidorova6
ElizavetaSamojlova6
YUliyaAvdeeva6
MatvejBogdanov6
IlyaFilippov6
DenisMel6
SvyatoslavMuravyov6
AnnaKulagina5
ZHannaFokina5
ValeriyaLapina5
ValentinaSazonova5
NataliyaMyasnikova5
ViktoriyaMakarova5
StanislavLazarev5
GennadijOvchinnikov5
RomanSHilov4
TimurSubbotin4
DanilaOsipov4
ArinaSilina4
NadezhdaZaharova4
LarisaSHCHerbakova4
AleksandraBelozyorova4
NatalyaDavydova4
MariyaFadeeva4
YUrijMarkov3
KirillSHubin3
GrigorijKolobov3
SemyonTrofimov3
VasilijUstinov3
ValentinaSHarova3
LarisaSavina3
GalinaOrekhova3
ArinaSHarapova2
ViktoriyaSergeeva2
VasilijKrasilnikov2
TimurRusakov2
GlebNesterov2
DenisMakarov2
ElizavetaSHilova2
VeraEvseeva1
MargaritaKabanova1
AngelinaLazareva1
SemyonVoronov1
InnokentijNekrasov1
ArtyomNikitin1
EgorBelyakov1

А теперь, чтобы вычислить сколько учащихся учится в каждом из классов и вывести эту информацию в новую колонку, мы можем применить оконную функцию:

MySQL
SELECT
    Student.first_name,
    Student.last_name,
    Student_in_class.class,
    COUNT(*) OVER (PARTITION BY Student_in_class.class) AS student_count_in_class
FROM
    Student_in_class
JOIN
    Student ON Student_in_class.student = Student.id;
first_namelast_nameclassstudent_count_in_class
EgorBelyakov17
ArtyomNikitin17
InnokentijNekrasov17
SemyonVoronov17
AngelinaLazareva17
MargaritaKabanova17
VeraEvseeva17
DenisMakarov27
ArinaSHarapova27
ViktoriyaSergeeva27
VasilijKrasilnikov27
TimurRusakov27
GlebNesterov27
ElizavetaSHilova27
KirillSHubin38
YUrijMarkov38
GrigorijKolobov38
SemyonTrofimov38
ValentinaSHarova38
LarisaSavina38
GalinaOrekhova38
VasilijUstinov38
TimurSubbotin49
RomanSHilov49
DanilaOsipov49
ArinaSilina49
NadezhdaZaharova49
LarisaSHCHerbakova49
AleksandraBelozyorova49
NatalyaDavydova49
MariyaFadeeva49
GennadijOvchinnikov58
StanislavLazarev58
ViktoriyaMakarova58
NataliyaMyasnikova58
ValentinaSazonova58
ValeriyaLapina58
ZHannaFokina58
AnnaKulagina58
IlyaFilippov611
SvyatoslavMuravyov611
DenisMel611
MatvejBogdanov611
YUliyaAvdeeva611
ElizavetaSamojlova611
PolinaSidorova611
LyubovZaharova611
IlyaAlekseev611
MatvejYAkushev611
SvyatoslavTarasov611
NinaOdincova710
BorisSimonov710
DmitrijTrofimov710
YAkovRozhkov710
FyodorDrozdov710
GlebStrelkov710
AngelinaLukina710
ValeriyaNovikova710
GrigorijKapustin710
VitalijPanfilov710
AnnaOsipova811
GeorgijBaranov811
YUliyaVishnyakova811
ValentinaBolshakova811
LeonidKryukov811
VladislavCvetkov811
LyubovBorisova811
AnfisaKalashnikova811
SnezhanaMorozova811
KristinaMyasnikova811
KristinaSmirnova811
VyacheslavEliseev913
IvanEfremov913
AnatolijZHdanov913
GeorgijNoskov913
ArtyomSergeev913
ArinaEvseeva913
AngelinaVoroncova913
EkaterinaUstinova913
RaisaLapina913
LeonidIgnatov913
SnezhanaSeliverstova913
SemyonBiryukov913
NikolajSokolov913

Что делает наша оконная функция

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

Функция COUNT же возвращает количество переданных в неё строк, тем самым мы и получаем сколько учащихся учится в каждом из классов.

Порядок выполнения оконных функций в SELECT

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

Очередь выполнения оконной функции в SELECT запросе

Заключение

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

И на последок давайте проверим все ли мы поняли:

Какое ключевое отличие между оконными функциями и агрегатными функциями с группировкой в SQL?