Баннер мобильный (3) Пройти тест

Оконные функции SQL: как работают OVER, PARTITION BY, ROW_NUMBER, RANK, LAG и LEAD

Как задавать окно для расчета, нумеровать строки, строить рейтинг и сравнивать соседние записи

Разбор

23 июня 2026

Поделиться

Скопировано
Оконные функции SQL: как работают OVER, PARTITION BY, ROW_NUMBER, RANK, LAG и LEAD

Содержание

    Когда нужно посчитать нарастающий итог, определить место записи в рейтинге или сравнить текущую строку с предыдущей, обычных агрегатных функций SQL уже недостаточно. Для таких задач используют оконные функции. Разберем, как устроено окно расчета, зачем нужны OVER и PARTITION BY и как применять ROW_NUMBER, RANK, LAG и LEAD на практике.

    Что такое оконные функции SQL и как они работают

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

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

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

    Из чего состоит оконная функция

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

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       SUM(amount) OVER () AS total_amount
    
    FROM payment;

    Здесь функция SUM(amount) OVER () считает сумму всех платежей из таблицы payment, которые попали в результат запроса.

    В результате у каждой строки появляется новый столбец total_amount с общей суммой, например:

    Реализация функции SUM(amount) OVER () в DBeaver

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

    Если нужно уточнить, какие строки участвуют в расчете и в каком порядке, внутри OVER(...) задают правила окна: PARTITION BY, ORDER BY, ROWS BETWEEN, RANGE BETWEEN.

    Разберем эти правила на примерах с функцией SUM().

    PARTITION BY

    PARTITION BY делит выборку на группы внутри оконного расчета. Например, можно посчитать сумму платежей отдельно для каждого клиента:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       SUM(amount) OVER (
    
           PARTITION BY customer_id
    
           ) AS customer_total_amount
    
    FROM payment;

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

    В результате каждая строка остается в таблице, а в новом столбце появляется сумма платежей именно этого клиента.

    Как работает запрос с PARTITION BY в DBeaver
    Результат выполнения запроса с PARTITION BY в DBeaver

    ORDER BY

    ORDER BY внутри OVER(…) задает порядок строк для оконного расчета. Он нужен, когда результат зависит от последовательности строк — например, от даты платежа, суммы продажи или времени события.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       SUM(amount) OVER (
    
           PARTITION BY customer_id
    
           ORDER BY payment_date
    
           ) AS running_total
    
    FROM payment;

    Здесь сумма считается отдельно по каждому клиенту и накапливается по дате платежа.

    Для первого платежа клиента в столбце running_total получим сумму первого платежа. Для второго платежа того же клиента получим сумму первого и второго платежа. Такой расчет называют нарастающим итогом.

    Результат выполнения запроса с ORDER BY в DBeaver
    Результат выполнения запроса с ORDER BY в DBeaver

    ROWS BETWEEN и RANGE BETWEEN

    ROWS BETWEEN и RANGE BETWEEN уточняют, какие строки внутри окна участвуют в расчете. Обычно эти конструкции используют вместе с ORDER BY, когда нужно посчитать значение по части строк, а не по всему разделу целиком.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       AVG(amount) OVER (
    
           PARTITION BY customer_id
    
           ORDER BY payment_date
    
           ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
    
           ) AS moving_avg
    
    FROM payment;

    Здесь ROWS BETWEEN 2 PRECEDING AND CURRENT ROW означает, что для каждой строки база данных берет текущую строку и две предыдущие строки в пределах того же клиента, а затем считает среднее значение amount.

    Если строк меньше, чем указано в условии, то используются только те строки, которые есть. Например, для первого платежа клиента среднее будет посчитано только по одной строке, для второго — по двум строкам.

    В этом запросе результат будет таким:

    • Для первого платежа клиента 1 среднее равно 100;
    • Для второго платежа клиента 1 среднее равно (100 + 200) / 2 = 150;
    • Для клиента 2, у которого только один платеж, среднее равно 150.
    Результат выполнения запроса с ROWS BETWEEN в DBeaver
    Результат выполнения запроса с ROWS BETWEEN в DBeaver

    Важно различать ROWS и RANGE:

    • ROWS считает строки по их количеству — например, две предыдущие строки, одна следующая строка, текущая строка;
    • RANGE ориентируется на значение в ORDER BY — то есть в расчет попадают строки, которые входят в заданный диапазон значений.

    Какие функции можно использовать с OVER

    Выше мы разбирали правила окна на примерах с SUM(), потому что сумму легко проверить по строкам. Но OVER работает и с другими функциями.

    Группа функций
    Примеры
    Что делают
    Агрегатные функции
    SUM, COUNT, AVG, MIN, MAX
    Считают сумму, количество, среднее, минимум или максимум по окну
    Функции нумерации
    ROW_NUMBER
    Присваивают строкам порядковые номера
    Функции ранжирования
    RANK, DENSE_RANK
    Показывают место строки в рейтинге
    Функции смещения
    LAG, LEAD
    Возвращают значение из предыдущей или следующей строки
    Функции распределения
    CUME_DIST, PERCENT_RANK, NTILE
    Показывают положение строки внутри распределения

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

    Агрегатные функции SUM, COUNT, AVG, MIN, MAX

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

    Например, COUNT(*) считает количество строк в итоговой выборке или группе. А COUNT(*) OVER (…) добавляет количество строк к каждой записи.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       COUNT(*) OVER (
    
           PARTITION BY customer_id
    
           ) AS customer_payments_count,
    
       MAX(amount) OVER (
    
           PARTITION BY customer_id
    
           ) AS customer_max_amount
    
    FROM payment;

    Здесь COUNT(*) OVER (PARTITION BY customer_id) считает количество платежей каждого клиента. MAX(amount) OVER (PARTITION BY customer_id) показывает самый крупный платеж этого клиента.

    Если у клиента два платежа, в обеих строках этого клиента будет одно и то же количество платежей и один и тот же максимум. При этом сами строки платежей останутся в результате.

    ROW_NUMBER

    ROW_NUMBER() нумерует строки внутри окна. Функция возвращает порядковый номер строки. Обычно ROW_NUMBER() используют вместе с ORDER BY, потому что номер зависит от порядка строк. Если добавить PARTITION BY, то нумерация будет начинаться заново внутри каждой группы.

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       ROW_NUMBER() OVER (
    
           PARTITION BY customer_id
    
           ORDER BY payment_date
    
           ) AS payment_number
    
    FROM payment;

    Здесь платежи нумеруются отдельно для каждого клиента. У клиента 1 первый платеж по дате получит номер 1, второй — номер 2. У клиента 2 нумерация начнется заново, поэтому его первый платеж тоже получит номер 1.

    Итоговая таблица будет выглядеть примерно так:

    customer_idpayment_dateamountpayment_number
    12024-01-101001
    12024-01-122002
    22024-01-151501

    RANK и DENSE_RANK

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

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

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

    customer_idpayment_dateamount
    12024-01-10200
    22024-01-12200
    32024-01-15150
    42024-01-18100
    • RANK() присваивает одинаковым значениям одно место. Следующее место считается с учетом количества строк выше, поэтому в рейтинге могут появляться пропуски. Например, если первое место заняли две строки, то следующая строка получит место 3.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       RANK() OVER (
    
           ORDER BY amount DESC
    
           ) AS payment_rank
    
    FROM payment;

    Результат:

    customer_idpayment_dateamountpayment_rank
    12024-01-102001
    22024-01-122001
    32024-01-151503
    42024-01-181004

    Два платежа по 200 делят первое место. Следующий платеж получает место 3, потому что первые две строки уже заняли две позиции в рейтинге.

    • DENSE_RANK() тоже присваивает одинаковым значениям одно место, но продолжает рейтинг без пропусков.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       DENSE_RANK() OVER (
    
           ORDER BY amount DESC
    
           ) AS dense_payment_rank
    
    FROM payment;

    Результат:

    customer_idpayment_dateamountdense_payment_rank
    12024-01-102001
    22024-01-122001
    32024-01-151502
    42024-01-181003

    Два платежа по 200 также делят первое место. Следующий платеж получает место 2, потому что DENSE_RANK() ведет рейтинг без пропущенных номеров.

    LAG и LEAD

    LAG() и LEAD() называют функциями смещения. Они позволяют получить значение из соседней строки — предыдущей (LAG) или следующей (LEAD).

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

    • LAG() берет значение из предыдущей строки.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       LAG(amount) OVER (
    
           PARTITION BY customer_id
    
           ORDER BY payment_date
    
           ) AS previous_amount
    
    FROM payment;

    Находим предыдущий платеж клиента. Здесь previous_amount показывает сумму предыдущего платежа того же клиента. У первого платежа клиента предыдущей строки нет, поэтому в результате будет NULL.

    В результате может получиться такая таблица:

    customer_idpayment_dateamountprevious_amount
    12024-01-10100NULL
    12024-01-12200100
    22024-01-15150NULL
    • LEAD() берет значение из следующей строки.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       LEAD(amount) OVER (
    
           PARTITION BY customer_id
    
           ORDER BY payment_date
    
           ) AS next_amount
    
    FROM payment;

    Здесь next_amount показывает сумму следующего платежа того же клиента. У последнего платежа клиента следующей строки нет, поэтому в результате будет NULL.

    Пример результата:

    customer_idpayment_dateamountnext_amount
    12024-01-10100200
    12024-01-12200NULL
    22024-01-15150NULL

    CUME_DIST, PERCENT_RANK и NTILE

    CUME_DIST(), PERCENT_RANK() и NTILE() помогают оценить положение строки внутри отсортированного набора данных. Их используют реже, чем ROW_NUMBER, RANK, LAG и LEAD, но их можно использовать для анализа распределения платежей, поиска верхней части рейтинга или деления клиентов на сегменты.

    Для примера возьмем такую таблицу:

    customer_idpayment_dateamount
    12024-01-10100
    22024-01-12150
    32024-01-15200
    42024-01-18200
    • CUME_DIST

    CUME_DIST() показывает, какая доля строк имеет значение меньше или равное текущему. Результат находится в диапазоне от 0 до 1.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       CUME_DIST() OVER (
    
           ORDER BY amount
    
           ) AS amount_distribution
    
    FROM payment;

    Результат:

    customer_idpayment_dateamountamount_distribution
    12024-01-101000.25
    22024-01-121500.50
    32024-01-152001.00
    42024-01-182001.00

    Здесь платеж 100 находится на первой позиции из четырех, поэтому получает значение 0.25. Платеж 150 находится на второй позиции, поэтому получает 0.50. Два платежа по 200 имеют максимальное значение, поэтому оба получают 1.00.

    • PERCENT_RANK

    PERCENT_RANK() показывает относительное место строки в рейтинге от 0 до 1. Чем ближе значение к 0, тем ближе строка к началу отсортированного набора. Чем ближе к 1, тем ближе строка к концу.

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       PERCENT_RANK() OVER (
    
           ORDER BY amount
    
           ) AS amount_percent_rank
    
    FROM payment;

    Результат:

    customer_idpayment_dateamountamount_percent_rank
    12024-01-101000.00
    22024-01-121500.33
    32024-01-152000.67
    42024-01-182000.67

    Здесь первый платеж получает 0.00, потому что он стоит в начале отсортированного набора. Платежи по 200 получают одинаковое значение, потому что занимают одно место в рейтинге.

    PERCENT_RANK() похож на CUME_DIST(), но считает положение иначе. CUME_DIST() учитывает долю строк до текущего значения включительно, а PERCENT_RANK() опирается на ранг строки в отсортированном наборе.

    • NTILE

    NTILE(n) делит строки на n примерно равных групп. Вместо n указывают количество групп.

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

    Пример:

    SELECT
    
       customer_id,
    
       payment_date,
    
       amount,
    
       NTILE(4) OVER (
    
           ORDER BY amount, customer_id
    
           ) AS amount_group
    
    FROM payment;

    Результат:

    customer_idpayment_dateamountamount_group
    12024-01-101001
    22024-01-121502
    32024-01-152003
    42024-01-182004

    Здесь NTILE(4) делит четыре платежа на четыре группы. Сначала строки сортируются по сумме платежа, а при одинаковых суммах дополнительно упорядочиваются по customer_id. Поэтому два платежа по 200 попадают в предсказуемом порядке: сначала клиент 3, затем клиент 4.

    Оконные функции SQL: коротко о главном

    • Оконные функции в SQL добавляют к каждой строке расчет, сделанный по заданному набору строк.
    • В отличие от GROUP BY, оконные функции сохраняют исходные строки в результате. Расчет добавляется отдельным столбцом.
    • Оконная функция состоит из двух частей — из функции и выражения OVER. Функция показывает, что нужно посчитать: сумму, количество, номер строки, место в рейтинге, предыдущее или следующее значение. OVER(…) показывает, по каким строкам нужно выполнить расчет. Если скобки после OVER пустые, то функция учитывает всю выборку.
    • PARTITION BY делит строки на группы внутри оконного расчета. Например, с его помощью можно считать сумму отдельно по каждому клиенту.
    • ORDER BY задает порядок строк внутри окна. Он нужен для нумерации, рейтингов, поиска соседних записей.
    • ROWS BETWEEN и RANGE BETWEEN уточняют границы окна. С их помощью можно считать значение по текущей строке и нескольким соседним строкам.
    • Агрегатные функции SUM, COUNT, AVG, MIN, MAX с OVER считают итог по окну и добавляют его к каждой строке.
    • ROW_NUMBER() присваивает строкам порядковые номера. Если использовать PARTITION BY, нумерация начинается заново внутри каждой группы.
    • RANK() и DENSE_RANK() показывают место строки в рейтинге. RANK() может оставлять пропуски в номерах, а DENSE_RANK() ведет рейтинг без пропусков.
    • LAG() берет значение из предыдущей строки, а LEAD() — из следующей. Эти функции используют для сравнения соседних записей.
    • CUME_DIST(), PERCENT_RANK() и NTILE() помогают оценить положение строки внутри распределения или разделить данные на группы.

    Разбор

    Поделиться

    Скопировано
    0 комментариев
    Комментарии