Оконные функции: вопросы для собеседования (часть 2)

Оконные функции — один из самых частых вопросов на собеседовании аналитика. ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, SUM OVER — всё это нужно знать наизусть. Интервьюеры проверяют, умеете ли вы решать задачи без подзапросов и самосоединений.

Агрегация, GROUP BY и HAVINGДаты и времяEXPLAIN и оптимизацияВыражения и NULLJOIN и операции множествОсновы SQL-запросовСтроки и приведение типовПодзапросы и CTE

Вопросы 610 из 20

6В таблице метрик есть `dt`, `platform`, `dau`. Нужно вывести значение `dau` за предыдущий день для той же платформы, чтобы посчитать дневное изменение. Какое выражение верное?
A`LEAD(dau) OVER (PARTITION BY platform ORDER BY dt)`
B`LAG(dau) OVER (PARTITION BY platform)` без `ORDER BY`
C`LAG(dau) OVER (ORDER BY dt)` без `PARTITION BY`
D`LAG(dau) OVER (PARTITION BY platform ORDER BY dt)`
Ответ: `LAG` берёт предыдущее значение по `ORDER BY` внутри каждой группы, заданной `PARTITION BY`.

Чтобы получить «вчерашний» `dau`, нужно определить порядок по времени через `ORDER BY dt`. Чтобы сравнение шло внутри одной платформы, нужно `PARTITION BY platform`. Поэтому корректно: `LAG(dau) OVER (PARTITION BY platform ORDER BY dt)`. Без сортировки понятие «предыдущий день» не определено, без разбиения по платформе вы будете сравнивать разные платформы, а `LEAD` берёт следующее значение, а не предыдущее.

Подробный разбор →
7Хотите добавить к каждой транзакции колонку «доля от общей суммы транзакций этого пользователя» и при этом не терять детализацию по транзакциям. Какой фрагмент корректен?
A`amount / SUM(amount)`
B`amount / SUM(amount) OVER (PARTITION BY user_id)`
C`amount / SUM(amount) OVER (ORDER BY paid_at)`
D`amount / SUM(amount) GROUP BY user_id`
Ответ: Оконная агрегация считает итог по группе и возвращает его в каждой строке, сохраняя гранулярность.

Фрагмент `amount / SUM(amount) OVER (PARTITION BY user_id)` считает общий `SUM(amount)` по каждому `user_id` и делит сумму конкретной транзакции на этот общий итог. Так сохраняются все строки транзакций, что важно для анализа вкладов/долей. Если использовать `GROUP BY user_id`, вы схлопнете данные до одной строки на пользователя и потеряете детализацию по транзакциям.

Подробный разбор →
8В таблице платежей для каждой транзакции нужен накопительный итог сумм пользователя на этот момент. Какое выражение даёт корректный результат?
A`SUM(amount) OVER (PARTITION BY user_id ROWS UNBOUNDED PRECEDING)`
B`SUM(amount) OVER (PARTITION BY user_id)` без `ORDER BY`
C`SUM(amount) OVER (ORDER BY paid_at)` без `PARTITION BY`
D`SUM(amount) OVER (PARTITION BY user_id ORDER BY paid_at)`
Ответ: Накопительный итог требует `ORDER BY` в окне, а раздельность по пользователям — `PARTITION BY`.

`SUM(amount) OVER (PARTITION BY user_id ORDER BY paid_at)` суммирует значения внутри каждого пользователя в порядке времени платежа и возвращает кумулятивный итог в каждой строке. Без `ORDER BY` получится общий итог по пользователю, повторённый в каждой строке. Без `PARTITION BY` накопление пойдёт по всем пользователям вместе, что ломает пользовательскую аналитику. Вариант с `GROUP BY` вообще схлопывает строки и делает накопление невозможным.

Подробный разбор →
9Вы пишете `LAG(price) OVER (PARTITION BY product_id)`, чтобы получить «вчерашнюю цену» товара по дням. Почему результат может оказаться неожиданным?
AБез `ORDER BY` понятие «предыдущая строка» в окне не определено, и значение `LAG` может быть из любой даты
B`LAG` без `ORDER BY` в окне возвращает `NULL` для всех строк секции, поэтому результат становится пустым
CВ одном запросе `LAG` следует сочетать только с `OVER ()` без `PARTITION BY`, иначе функция неверно работает
DНужно добавить `GROUP BY product_id` к запросу, иначе `LAG` блокируется внутри секционированного окна
Ответ: Для `LAG` обязателен осмысленный порядок через `ORDER BY` в окне.

Окно `OVER (PARTITION BY product_id)` задаёт группу строк товара, но не задаёт их порядок. Без `ORDER BY dt` невозможно определить, какая строка «вчерашняя», поэтому `LAG(price)` может вернуть значение из любой даты внутри секции. Чтобы анализировать динамику, используйте `LAG(price) OVER (PARTITION BY product_id ORDER BY dt)` (и при совпадениях добавьте дополнительный ключ сортировки). `LAG` свободно сочетается с `PARTITION BY`, не возвращает `NULL` автоматически и не требует `GROUP BY`.

Подробный разбор →
10Вы делаете рейтинг товаров по выручке внутри категории. Если два товара делят 2 место, следующий товар должен получить 3 место (без пропуска). Какая функция подходит лучше всего?
A`DENSE_RANK`
B`RANK`
C`ROW_NUMBER`
D`LEAD`
Ответ: `DENSE_RANK` присваивает одинаковое место равным значениям и не делает пропусков в нумерации.

`DENSE_RANK()` дает одинаковый ранг равным значениям, а следующий ранг увеличивает на 1: 1, 2, 2, 3... Это подходит, когда места должны идти без пропусков. В `RANK()` после равенства появляются пропуски (1, 2, 2, 4...). `ROW_NUMBER()` вообще не выделяет одинаковые места: у каждой строки свой номер.

Подробный разбор →
1234

Хотите тренировать интерактивно?

В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.

Тренировать SQL в Telegram

Другие темы: SQL

Агрегация, GROUP BY и HAVINGДаты и времяEXPLAIN и оптимизацияВыражения и NULLJOIN и операции множествОсновы SQL-запросовСтроки и приведение типовПодзапросы и CTE