Оконные функции SQL: шпаргалка для собеседования
Зачем аналитику оконные функции
Оконные функции — самая частая тема на собеседованиях для middle-аналитиков. Если знаете только GROUP BY — этого мало.
Оконные функции — тема номер один на техническом собеседовании аналитика уровня middle и выше. Они позволяют делать вычисления по группам строк, не сворачивая результат в одну строку, как GROUP BY. Это незаменимо для расчёта рангов, нарастающих итогов, скользящих средних и сравнения строк между собой.
Если вы готовитесь к собеседованию по SQL, оконные функции — первое, что нужно довести до автоматизма.
Общий синтаксис
функция() OVER (
PARTITION BY столбец_группировки
ORDER BY столбец_сортировки
ROWS BETWEEN начало AND конец
)- PARTITION BY — делит данные на группы (окна). Аналог GROUP BY, но без свёртки строк.
- ORDER BY — задаёт порядок строк внутри окна.
- ROWS BETWEEN — определяет рамку: какие строки участвуют в вычислении.
Не все части обязательны — зависит от конкретной функции.
Функции ранжирования
ROW_NUMBER()
Присваивает уникальный порядковый номер каждой строке внутри окна.
SELECT
user_id,
order_date,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_date) AS order_num
FROM ordersРезультат: у каждого пользователя заказы пронумерованы 1, 2, 3… по дате. Номера всегда уникальны, даже при одинаковых значениях order_date.
На собеседовании спрашивают: «Найдите первый заказ каждого пользователя». Решение — обернуть в подзапрос и отфильтровать WHERE order_num = 1.
RANK()
Присваивает ранг с учётом одинаковых значений. При дубликатах — одинаковый ранг, следующий номер пропускается.
SELECT
student_id,
score,
RANK() OVER (ORDER BY score DESC) AS rank
FROM exam_resultsЕсли у двух студентов балл 95, оба получат ранг 1. Следующий студент получит ранг 3 (не 2).
DENSE_RANK()
Как RANK(), но без пропусков в нумерации.
SELECT
student_id,
score,
DENSE_RANK() OVER (ORDER BY score DESC) AS dense_rank
FROM exam_resultsДва студента с баллом 95 — ранг 1. Следующий — ранг 2.
Классический вопрос на собеседовании: «Чем отличаются ROW_NUMBER, RANK и DENSE_RANK?» Ответ: ROW_NUMBER всегда уникален, RANK пропускает номера при дубликатах, DENSE_RANK не пропускает.
NTILE(n)
Делит строки на n равных групп.
SELECT
user_id,
total_spent,
NTILE(4) OVER (ORDER BY total_spent DESC) AS quartile
FROM user_statsПользователи разбиты на 4 квартиля по сумме покупок. Первый квартиль — топ-25% по расходам.
На собеседовании: используется в задачах про сегментацию — «Разделите пользователей на 10 равных групп по активности».
Функции смещения
LAG(столбец, смещение)
Возвращает значение из предыдущей строки (или на n строк назад).
SELECT
DATE,
revenue,
LAG(revenue, 1) OVER (ORDER BY DATE) AS prev_day_revenue,
revenue - LAG(revenue, 1) OVER (ORDER BY DATE) AS daily_change
FROM daily_statsПолучаем выручку за предыдущий день и разницу — рост или падение.
LEAD(столбец, смещение)
Обратная LAG — берёт значение из следующей строки.
SELECT
user_id,
session_date,
LEAD(session_date, 1) OVER (PARTITION BY user_id ORDER BY session_date) AS next_session
FROM sessionsДля каждой сессии пользователя видим дату следующей сессии. Если следующей нет — NULL.
На собеседовании: «Посчитайте среднее время между сессиями пользователя». Решение — через LAG или LEAD вычислить разницу дат, затем AVG.
Агрегатные функции с окном
SUM() OVER
Нарастающий итог — одна из самых частых задач.
SELECT
DATE,
new_users,
SUM(new_users) OVER (ORDER BY DATE) AS cumulative_users
FROM daily_registrationsКаждая строка показывает сумму всех предыдущих значений включая текущее — нарастающий итог регистраций.
Нарастающий итог по группам:
SELECT
user_id,
order_date,
amount,
SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date) AS running_total
FROM ordersAVG() OVER
Скользящее среднее — незаменимо для сглаживания метрик.
SELECT
DATE,
dau,
AVG(dau) OVER (
ORDER BY DATE
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS dau_7d_avg
FROM daily_metricsСреднее DAU за последние 7 дней (текущий + 6 предыдущих). Именно так считают скользящее среднее в продуктовой аналитике.
Оконные рамки: ROWS BETWEEN
Рамка определяет, какие строки участвуют в вычислении относительно текущей.
| Синтаксис | Значение |
|---|---|
UNBOUNDED PRECEDING |
С начала окна |
n PRECEDING |
n строк назад |
CURRENT ROW |
Текущая строка |
n FOLLOWING |
n строк вперёд |
UNBOUNDED FOLLOWING |
До конца окна |
Примеры:
-- Сумма текущей + 2 предыдущих строки
SUM(x) OVER (ORDER BY DATE ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
-- Среднее в окне ±1 строка от текущей
AVG(x) OVER (ORDER BY DATE ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
-- Нарастающий итог с начала
SUM(x) OVER (ORDER BY DATE ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)Важный нюанс: по умолчанию (без явного ROWS BETWEEN) при наличии ORDER BY рамка = RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. Это ведёт себя иначе при дубликатах в ORDER BY. На собеседовании этот вопрос задают, чтобы проверить глубину понимания.
Комбинированные задачи
На собеседованиях часто просят совместить несколько оконных функций. Пример:
Задача: Для каждого пользователя найдите заказ с максимальной суммой и выведите долю этого заказа от общей суммы покупок.
WITH ranked AS (
SELECT
user_id,
order_id,
amount,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rn,
SUM(amount) OVER (PARTITION BY user_id) AS total_amount
FROM orders
)
SELECT
user_id,
order_id,
amount,
ROUND(amount::numeric / total_amount * 100, 1) AS pct_of_total
FROM ranked
WHERE rn = 1Здесь ROW_NUMBER находит максимальный заказ, а SUM OVER считает общую сумму по пользователю — обе функции в одном запросе.
Как тренироваться
Оконные функции нужно не просто понять — их нужно написать руками десятки раз. Теория забывается, навык остаётся.
Тренажёр Карьерник содержит вопросы по оконным функциям с разборами: от базового отличия RANK и ROW_NUMBER до сложных комбинаций с ROWS BETWEEN. Можно тренироваться по 15 минут в день в Telegram — этого достаточно, чтобы за неделю довести тему до автоматизма.
Больше примеров вопросов по SQL — в разделе подготовки. А если готовитесь комплексно, посмотрите примеры вопросов по всем темам.
Совет: выучите шаблон OVER(PARTITION BY ... ORDER BY ...). 80% оконных вопросов на собеседовании — это вариации этого шаблона.
FAQ
Какие оконные функции чаще всего спрашивают на собеседовании?
ROW_NUMBER, RANK и LAG/LEAD — абсолютные лидеры. За ними идут SUM OVER для нарастающих итогов и AVG OVER для скользящих средних. NTILE встречается реже, но появляется в задачах на сегментацию. Обязательно знайте разницу между ROW_NUMBER, RANK и DENSE_RANK — этот вопрос задают почти всегда.
Чем ROWS отличается от RANGE в оконной рамке?
ROWS считает физические строки: ROWS BETWEEN 2 PRECEDING AND CURRENT ROW — это ровно 3 строки. RANGE работает по значениям: если в ORDER BY есть дубликаты, RANGE включит все строки с одинаковым значением. На практике почти всегда нужен ROWS. Если не указать рамку явно, по умолчанию используется RANGE — это частая ловушка на собеседованиях.
Можно ли использовать несколько оконных функций в одном запросе?
Да, и это делают постоянно. Каждая оконная функция может иметь свой PARTITION BY и ORDER BY. Например, в одном SELECT можно одновременно пронумеровать строки через ROW_NUMBER и посчитать нарастающую сумму через SUM OVER — они будут работать независимо.