среда, 13 марта 2024 г.

Оконные функции aka over-функции

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

Обычные, или скалярные, функции принимают на вход значения столбцов одной — текущей — строки курсора. Агрегатные функции принимают на вход значения столбцов всех строк курсора, определяемых where и group by (или их отсутствием). Оконные функции принимают на вход значения столбцов множества строк, определяемого в предложении over и зависящего от текущей строки.

Рассмотрим оконные функции на примере СУБД PosgreSQL 15.

Для удобства обзора, разделим все оконные функции на три группы:

  • агрегатные функции,
  • ранжирующие функции,
  • относительные функции.

Обычные агрегатные функции, кроме функций на упорядоченных множествах (с предложением within group), можно использовать как оконные функции, добавив к их вызову предложение over.

Ранжирующие и относительные функции нужно обязательно вызывать с предложением over.

Агрегатные функции

Ниже список агрегатных функций PosrgreSQL 15, исключая статистические функции и функции на упорядоченных множествах. Подробнее см. Aggregate Functions.

ФункцияОписание
array_aggМассив значений.
avgСреднее арифметическое.
bit_andПобитовое И.
bit_orПобитовое ИЛИ.
bit_xorПобитовое исключающее ИЛИ.
bool_andЛогическое И.
bool_orЛогическое ИЛИ.
countСчетчик значений.
everyСтандартный эквивалент bool_and.
json_aggМассив json.
jsonb_aggМассив jsonb.
json_object_aggОбъект json.
jsonb_object_aggОбъект jsonb.
maxМаксимальное значение.
minМинимальное значение.
range_aggОбъединение диапазонов.
range_intersect_aggПересечение диапазонов.
string_aggКонкатенация строк.
sumСумма значений.
xmlaggАгрегат xml.

И пара примеров их использования в качестве оконных.

Агрегатная функция с order by в предложении over и оконной рамкой (window frame) по умолчанию производит накопительный эффект от строки к строке результирующей таблицы.

select n,
    count(n) over (order by n) count_,
    sum(n) over (order by n) sum_,
    avg(n) over (order by n) avg_
from
    generate_series(1,5) as t(n)
;
ncount_sum_avg_
1111.0
2231.5
3362.0
44102.5
55153.0
select n,
    string_agg(n::text, ', ') over (order by n) asc_,
    string_agg(n::text, ', ') over (order by n desc) desc_
from
    generate_series(1,5) as t(n)
;
nasc_ desc_
11 5, 4, 3, 2, 1
21, 2 5, 4, 3, 2
31, 2, 3 5, 4, 3
41, 2, 3, 4 5, 4
51, 2, 3, 4, 55

Ранжирующие функции

Ниже список ранжирующих функций PosrgreSQL 15 и пара примеров их использования. Подробнее см. Window Functions.

ФункцияОписание
row_numberНомер текущей строки.
rankРанг текущей строки, с пропусками; это номер первой строки в группе равнозначных ей (its peer group).
dense_rankРанг текущей строки, без пропусков; это номер группы равнозначных строк (peer groups).
percent_rankРанг текущей строки в диапазоне от 0 до 1.
cume_distКумулятивное распределение в диапазоне от 1/(число строк в окне) to 1.
ntile(arg)Номер группы (bucket) от 1 до arg, в которую попадает текущая строка.

Все перечисленные функции нуждаются в упорядочивании входных данных с помощью order by в предложении over. Строки, которые не отличаются друг от друга значениями столбцов в order by, называются равнозначными (peers).

Функции rank, dense_rank, percent_rank и cume_dist возвращают одинаковые значения для всех строк из группы равнозначных.

select n,
       dense_rank() over (order by n) dense_rank_,
       percent_rank() over (order by n) percent_rank_,
       cume_dist() over (order by n) cume_dist_
from 
    generate_series(1,5) as t(n)
;
ndense_rank_percent_rank_cume_dist_
110.0 0.2
220.250.4
330.5 0.6
440.750.8
551.0 1.0
select n,
       ntile(2) over (order by n) ntile_2,
       ntile(3) over (order by n) ntile_3
from 
    generate_series(1,10) as t(n)
;
nntile_2ntile_3
111
211
311
411
512
622
722
823
923
1023

Относительные функции

Ниже список относительных функций PosrgreSQL 15 и пара примеров их использования. Подробнее см. Window Functions.

ФункцияОписание
lagЗначение из строки, предшествующей текущей.
leadЗначение из строки, следующей за текущей.
first_valueЗначение из первой строки окна (window frame).
last_valueЗначение из последней строки окна (window frame).
nth_valueЗначение из n-ной строки окна (window frame).
select n,
    lag(n) over (order by n) lag_,
    lead(n) over (order by n) lead_
from 
    generate_series(1,5) as t(n)
;
nlag_lead_
1 2
213
324
435
54

Заметьте, что функции first_value, last_value и nth_value имеют дело только со строками в оконной рамке (window frame), которая по умолчанию содержит строки с первой строки окна до последней из равнозначных текущей строке.

select n,
    first_value(n) over (order by n) first_,
    nth_value(n, 3) over (order by n) nth_,
    last_value(n) over (order by n) last_
from 
    generate_series(1,5) as t(n)
;
nfirst_nth_last_
11 1
21 2
3133
4134
5135

Оконные рамки

Оконная рамка (window frame) определяет часть строк окна, на которых работает оконная функция.

В следующем примере значения столбцов first_ и last_ вычисляются в оконной рамке по умолчанию, а значения столбцов first_x и last_x вычисляются в оконной рамке, включающей текущую строку и все строки до конца окна:

select n,
    first_value(n) over (order by n) first_,
    first_value(n) over (order by n rows between current row and unbounded following) first_x,
    last_value(n) over (order by n) last_,
    last_value(n) over (order by n rows between current row and unbounded following) last_x
from 
    generate_series(1,5) as t(n)
;
n first_first_xlast_ last_x
11115
21225
31335
41445
51555

Сузив оконные рамки до одной предыдущей и одной последующей строки, сымитируем функции lag и lead с помощью функций first_value и last_value:

select n,
    first_value(n) over (order by n rows between 1 preceding and 1 preceding) lag_,
    last_value(n) over (order by n rows between 1 following and 1 following) lead_
from 
    generate_series(1,5) as t(n)
;
nlag_lead_
1 2
213
324
435
54

Помимо rows, оконная рамка может определяться через range и groups. Подробнее см. описание frame_clause в документации PostgreSQL.

Больше окон, полезных и разных

В вышеприведенных примерах оконные функции работают в рамках одного окна, которое включает все строки, порождаемые частями from и where запроса select.

Но предложение partition by в over позволяет разбить это множество строк на группы, чтобы оконные функции работали отдельно со строками каждой из них – аналогично тому, как group by порождает группы строк для агрегатных функций. Таким образом, partition by разбивает множество строк на ряд окон.

Рассмотрим такой набор данных:

select
    case n % 3
        when 0 then
            'A'
        when 1 then
            'B'
        when 2 then
            'C'
    end item,
    n
from generate_series(1, 9) as t(n)
;
itemn
B1
C2
A3
B4
C5
A6
B7
C8
A9

Тогда все множество строк можно разбить на группы по значению item:

select
    item,
    n,
    count(n) over (partition by item order by n) count_,
    lag(n) over (partition by item order by n) lag_
from (
    select
        case n % 3
            when 0 then
                'A'
            when 1 then
                'B'
            when 2 then
                'C'
        end item,
        n
    from generate_series(1, 9) as t(n)
) t
;
itemncount_lag_
A31
A623
A936
B11
B421
B734
C21
C522
C835

Подробнее см. описание window_definition в документации PostgreSQL.

Что дальше

Оконные функции, а частности, используются при построении запросов вида "пробелы и острова", или "gaps and islands". См. мой пост Пробелы и острова, или Gaps and islands. Часть I.

Ранжирующие функции, а частности, помогают строить запросы вида "верхние-n", или "top-n", где требуется получить наименьшие или наибольшие по некоторому критерию n строк.

SQL неисчерпаем!

Комментариев нет:

Отправить комментарий