SQL оконные функции: ROW_NUMBER, RANK и LAG

Категория: Postgresql · Язык: SQL · Обновлено:

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

Нумерация заказов внутри клиента

SQL
CREATE TEMP TABLE orders (customer_id int, order_id int, amount numeric, created_at date);
INSERT INTO orders VALUES
  (1, 101, 50, '2026-01-01'),
  (1, 102, 75, '2026-01-02'),
  (2, 201, 30, '2026-01-01');

SELECT customer_id, order_id,
       ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY created_at) AS row_num
FROM orders
ORDER BY customer_id, created_at;
-- ожидаемый row_num: 1, 2, 1

PARTITION BY начинает нумерацию заново для каждого клиента. ORDER BY внутри окна задаёт порядок расчёта.

Сравнить rank и dense_rank

SQL
SELECT amount,
       RANK() OVER (ORDER BY amount DESC) AS rank_with_gaps,
       DENSE_RANK() OVER (ORDER BY amount DESC) AS rank_without_gaps
FROM (VALUES (100), (100), (80)) AS scores(amount)
ORDER BY amount DESC;

При равных значениях RANK() оставляет пропуск в следующем месте, а DENSE_RANK() — нет.

Взять значение из предыдущей строки

SQL
WITH orders(customer_id, order_id, amount, created_at) AS (
  VALUES (1, 101, 50, DATE '2026-01-01'),
         (1, 102, 75, DATE '2026-01-02'),
         (2, 201, 30, DATE '2026-01-01')
)
SELECT customer_id, order_id, amount,
       LAG(amount) OVER (PARTITION BY customer_id ORDER BY created_at) AS previous_amount
FROM orders;

Функция

Результат

ROW_NUMBER()

Уникальный порядковый номер

RANK()

Ранг с пропусками при равных значениях

DENSE_RANK()

Ранг без пропусков

LAG(value)

Значение из предыдущей строки окна

Чем окно отличается от GROUP BY?

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

Как получить сумму с накоплением?

Используйте SUM(amount) OVER (PARTITION BY customer_id ORDER BY created_at ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW).