Оконные функции — один из самых частых вопросов на собеседовании аналитика. ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, SUM OVER — всё это нужно знать наизусть. Интервьюеры проверяют, умеете ли вы решать задачи без подзапросов и самосоединений.
Чтобы получить «вчерашний» `dau`, нужно определить порядок по времени через `ORDER BY dt`. Чтобы сравнение шло внутри одной платформы, нужно `PARTITION BY platform`. Поэтому корректно: `LAG(dau) OVER (PARTITION BY platform ORDER BY dt)`. Без сортировки понятие «предыдущий день» не определено, без разбиения по платформе вы будете сравнивать разные платформы, а `LEAD` берёт следующее значение, а не предыдущее.
Подробный разбор →Фрагмент `amount / SUM(amount) OVER (PARTITION BY user_id)` считает общий `SUM(amount)` по каждому `user_id` и делит сумму конкретной транзакции на этот общий итог. Так сохраняются все строки транзакций, что важно для анализа вкладов/долей. Если использовать `GROUP BY user_id`, вы схлопнете данные до одной строки на пользователя и потеряете детализацию по транзакциям.
Подробный разбор →`SUM(amount) OVER (PARTITION BY user_id ORDER BY paid_at)` суммирует значения внутри каждого пользователя в порядке времени платежа и возвращает кумулятивный итог в каждой строке. Без `ORDER BY` получится общий итог по пользователю, повторённый в каждой строке. Без `PARTITION BY` накопление пойдёт по всем пользователям вместе, что ломает пользовательскую аналитику. Вариант с `GROUP 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`.
Подробный разбор →`DENSE_RANK()` дает одинаковый ранг равным значениям, а следующий ранг увеличивает на 1: 1, 2, 2, 3... Это подходит, когда места должны идти без пропусков. В `RANK()` после равенства появляются пропуски (1, 2, 2, 4...). `ROW_NUMBER()` вообще не выделяет одинаковые места: у каждой строки свой номер.
Подробный разбор →В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.
Тренировать SQL в Telegram