← Назад к вопросам

Какие знаешь виды оконных функций?

1.0 Junior🔥 231 комментариев
#Базы данных и SQL

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

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Какие знаешь виды оконных функций?

Оконные функции (Window Functions) — это мощный инструмент в SQL для анализа данных. Они позволяют выполнять вычисления в контексте "окна" (набора строк) без группирования результатов, как в GROUP BY. Знание этих функций критично для System Analyst, работающего с аналитикой и оптимизацией запросов.

Категория 1: Агрегирующие функции (Aggregate Window Functions)

Используют агрегацию в рамках окна без свёртывания результата:

SUM() OVER

Нарастающая сумма по датам:

SELECT 
  date,
  amount,
  SUM(amount) OVER (ORDER BY date) as running_total
FROM sales
ORDER BY date;

-- Результат:
-- 2025-01-01 | 100 | 100
-- 2025-01-02 | 150 | 250
-- 2025-01-03 | 200 | 450

AVG() OVER

Средняя цена за последние 7 дней (скользящее окно):

SELECT 
  date,
  price,
  AVG(price) OVER (
    ORDER BY date 
    ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
  ) as avg_7days
FROM stock_prices;

COUNT() OVER

Количество заказов пользователя к текущей дате:

SELECT 
  user_id,
  order_date,
  COUNT(*) OVER (PARTITION BY user_id ORDER BY order_date) as order_number
FROM orders;

-- Результат: порядковый номер заказа каждого пользователя

MIN() / MAX() OVER

Минимальная и максимальная цена в период:

SELECT 
  date,
  price,
  MIN(price) OVER (ORDER BY date ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) as min_30d,
  MAX(price) OVER (ORDER BY date ROWS BETWEEN 29 PRECEDING AND CURRENT ROW) as max_30d
FROM stock_prices;

Категория 2: Функции ранжирования (Ranking Functions)

Определяют позицию строки в наборе.

ROW_NUMBER()

Уникальный номер каждой строки (без пропусков):

SELECT 
  user_id,
  score,
  ROW_NUMBER() OVER (ORDER BY score DESC) as rank
FROM leaderboard;

-- Результат: 1, 2, 3, 4, 5... (без пропусков)

RANK()

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

SELECT 
  user_id,
  score,
  RANK() OVER (ORDER BY score DESC) as rank
FROM leaderboard;

-- Если два пользователя имеют score 100:
-- rank: 1, 1, 3 (пропускается 2)

DENSE_RANK()

Ранг БЕЗ пропусков при равных значениях:

SELECT 
  user_id,
  score,
  DENSE_RANK() OVER (ORDER BY score DESC) as rank
FROM leaderboard;

-- Если два пользователя имеют score 100:
-- rank: 1, 1, 2 (без пропусков)

NTILE()

Разделяет данные на N групп (квартили, децили):

SELECT 
  user_id,
  revenue,
  NTILE(4) OVER (ORDER BY revenue DESC) as quartile
FROM users;

-- quartile: 1 (top 25%), 2 (next 25%), 3, 4

Категория 3: Функции смещения (Offset Functions)

Доступ к значениям соседних строк.

LAG()

Предыдущее значение (для сравнения день с днём):

SELECT 
  date,
  revenue,
  LAG(revenue) OVER (ORDER BY date) as prev_day_revenue,
  revenue - LAG(revenue) OVER (ORDER BY date) as day_change
FROM daily_stats;

-- Результат: видим день в день рост/падение

LEAD()

Следующее значение (для прогноза):

SELECT 
  date,
  price,
  LEAD(price, 1) OVER (ORDER BY date) as next_day_price
FROM stock;

FIRST_VALUE() / LAST_VALUE()

Первое и последнее значение в окне:

SELECT 
  month,
  revenue,
  FIRST_VALUE(revenue) OVER (ORDER BY month) as first_month,
  LAST_VALUE(revenue) OVER (
    ORDER BY month 
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) as last_month
FROM monthly_revenue;

Примечание: важно указать UNBOUNDED FOLLOWING, иначе LAST_VALUE вернёт текущую строку!

NTH_VALUE()

N-ое значение в окне:

SELECT 
  date,
  price,
  NTH_VALUE(price, 3) OVER (ORDER BY date) as 3rd_price
FROM prices;

Категория 4: Функции распределения (Distribution Functions)

PERCENT_RANK()

Относительный ранг (от 0 до 1):

SELECT 
  user_id,
  score,
  PERCENT_RANK() OVER (ORDER BY score) as percentile
FROM leaderboard;

-- Результат: 0.0 (первый), 0.5 (середина), 1.0 (последний)

CUME_DIST()

Кумулятивное распределение:

SELECT 
  user_id,
  salary,
  CUME_DIST() OVER (ORDER BY salary) as cume_dist
FROM employees;

-- Показывает, какой % сотрудников зарабатывает меньше или равно текущему

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

function_name() OVER (
  [PARTITION BY column1, column2, ...]  -- разделить данные на группы
  [ORDER BY column1 ASC/DESC, ...]       -- порядок внутри окна
  [frame_specification]                  -- размер окна
)

PARTITION BY

Разделяет данные на независимые окна:

SELECT 
  department,
  employee,
  salary,
  AVG(salary) OVER (PARTITION BY department) as dept_avg
FROM employees;

-- Средняя зарплата рассчитывается отдельно для каждого отдела

ORDER BY

Определяет порядок и часто указывает направление (например, для наростающей суммы):

SUM(amount) OVER (ORDER BY date)  -- нарастающая
SUM(amount) OVER (PARTITION BY user_id ORDER BY date)  -- наростающая по пользователю

Frame Specification

Определяет, какие строки включаются в расчёт:

ROWS BETWEEN 6 PRECEDING AND CURRENT ROW  -- 7 строк (6 до + текущая)
ROWS UNBOUNDED PRECEDING AND CURRENT ROW   -- от первой до текущей
RANGE BETWEEN INTERVAL '7 days' PRECEDING AND CURRENT ROW  -- 7 дней

Практические примеры

Пример 1: Когорта анализ

Найти когорту пользователей по месяцу первой покупки:

SELECT 
  user_id,
  purchase_date,
  DATE_TRUNC('month', 
    FIRST_VALUE(purchase_date) OVER (PARTITION BY user_id ORDER BY purchase_date)
  ) as cohort_month,
  ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY purchase_date) as purchase_number
FROM orders;

Пример 2: Выявить гэпы в данных

SELECT 
  date,
  LEAD(date) OVER (ORDER BY date) - date as gap_days
FROM events
WHERE LEAD(date) OVER (ORDER BY date) - date > 1;

Пример 3: Top N per Group

Топ 3 товара по выручке в каждой категории:

WITH ranked AS (
  SELECT 
    category,
    product,
    revenue,
    RANK() OVER (PARTITION BY category ORDER BY revenue DESC) as rank
  FROM sales
)
SELECT * FROM ranked WHERE rank <= 3;

Производительность

Плюсы оконных функций:

  • Одна строка результата на одну строку входа (vs GROUP BY)
  • Один scan таблицы
  • Нет необходимости в JOIN к себе

Оптимизация:

-- Хорошо: с индексом на (user_id, date)
SELECT 
  user_id,
  date,
  SUM(amount) OVER (PARTITION BY user_id ORDER BY date) as running_total
FROM orders;

-- Плохо: без индекса → full scan + sort

Когда НЕ использовать оконные функции

  • Если нужна агрегация в одну строку → используй GROUP BY
  • Если performance критичен и нет индексов → оптимизируй индексы
  • Если нужно множество разных окон → consider materialized views

Оконные функции — это essential skill для System Analyst, работающего с большими данными и аналитикой.

Какие знаешь виды оконных функций? | PrepBro