PARTITION BY и ORDER BY
PARTITION BY делит строки на независимые окна; ORDER BY внутри OVER упорядочивает строки внутри каждого. Подвох: добавление ORDER BY тихо меняет фрейм по умолчанию с целой партиции на накопительный диапазон.
Выкатывается отчёт по выручке. Финансы помечают его на следующее утро: колонка «итог региона» неверна — она показывает накопительный итог, который растёт вниз по странице, вместо одинакового плоского итога на регион. В запросе было SUM(amount) OVER (PARTITION BY region ORDER BY sale_date). Накопительного итога никто не просил. Но добавление ORDER BY внутрь OVER тихо превратило агрегат в него. Этот урок о двух клаузах, формирующих окно, и о ловушке во второй.
PARTITION BY: независимые окна бок о бок
PARTITION BY — пространственное измерение окна. Он разрезает результат на группы и вычисляет оконную функцию независимо внутри каждой группы, сбрасываясь на каждой границе. Думай о каждой партиции как о своём приватном окне, ничего не знающем про остальные.
SELECT region, salesperson, amount,
SUM(amount) OVER (PARTITION BY region) AS region_total,
AVG(amount) OVER (PARTITION BY region) AS region_avg
FROM sales;Каждая строка West видит только строки West; каждая строка East — только East. region_total для продажи West — это сумма всех сумм West и ничего больше. Это ровно та роль, которую GROUP BY region играет в группированном запросе — но здесь строки никогда не схлопываются. Опусти PARTITION BY, и весь результат становится одной гигантской партицией (общий итог по всем строкам).
ORDER BY внутри OVER: порядок внутри партиции
ORDER BY внутри клаузы OVER (...) — временное измерение: он задаёт порядок строк внутри каждой партиции. Это полностью отдельно от внешнего ORDER BY запроса, который лишь сортирует финальный вывод. Внутренний ORDER BY окна — это то, что делает возможными «накопительные» и «относительно строки» вычисления: он даёт строкам последовательность, чтобы функция могла сказать «всё до сюда», «предыдущая строка», «ранг на текущий момент».
SELECT region, sale_date, amount,
ROW_NUMBER() OVER (PARTITION BY region ORDER BY sale_date) AS nth_sale,
SUM(amount) OVER (PARTITION BY region ORDER BY sale_date) AS running_total
FROM sales;ROW_NUMBER() нужен порядок, чтобы нумеровать 1, 2, 3 внутри каждого региона по дате. SUM с тем же окном теперь выдаёт накопительный итог — и вот это сюрприз.
Ловушка фрейма по умолчанию
Вот правило, которое кусает. Когда у OVER нет ORDER BY, фрейм окна — срез строк, которые функция реально агрегирует — по умолчанию равен всей партиции. Поэтому SUM(amount) OVER (PARTITION BY region) суммирует каждую строку региона: одно плоское число, повторённое.
В тот миг, когда ты добавляешь ORDER BY внутрь OVER, фрейм по умолчанию тихо меняется на RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW — «от первой строки партиции до текущей строки». Это накопительный агрегат, а не агрегат на всю партицию. Тот же SUM, тот же PARTITION BY, но добавление внутреннего ORDER BY переворачивает плоский итог в накопительный.
-- Плоский итог региона (без внутреннего ORDER BY): то же значение на каждой строке West
SUM(amount) OVER (PARTITION BY region)
-- Накопительный итог (внутренний ORDER BY): растёт вниз по партиции
SUM(amount) OVER (PARTITION BY region ORDER BY sale_date)Это и есть баг из Хука. Автор хотел плоский итог региона, но добавил ORDER BY sale_date (возможно, скопировав у соседнего ROW_NUMBER), и фрейм тихо стал накопительным. Фикс — убрать внутренний ORDER BY для плоского итога, или — если одним колонкам порядок нужен, а другим нет — назвать окна явно, чтобы каждое получило нужный фрейм.
▸Частая ошибка
Самая глубокая часть этой ловушки в том, что она тихая — ни ошибки, ни предупреждения, просто число, которое слегка неверно и проходит ревью, потому что SQL выглядит разумно. Две сеньорские привычки защищают от этого. Первая: если хочешь плоский агрегат — никогда не клади ORDER BY в это окно. Вторая: когда запрос смешивает плоские и накопительные агрегаты, определяй именованные окна через WINDOW w_flat AS (PARTITION BY region) и WINDOW w_run AS (PARTITION BY region ORDER BY sale_date), а затем ссылайся OVER w_flat / OVER w_run. Следующий урок, о фреймах, делает поведение «накопительный против плоского» полностью явным.
Что делает PARTITION BY region внутри клаузы OVER?
Ты хочешь ПЛОСКИЙ итог региона на каждой строке, но написал SUM(amount) OVER (PARTITION BY region ORDER BY sale_date). Что пойдёт не так?
Заполни пропуск: без внутреннего ORDER BY фрейм окна — вся партиция (плоский итог). Добавление ORDER BY внутри OVER тихо меняет фрейм по умолчанию на _______ диапазон, превращая плоский агрегат в накопительный.
- 01В чём разница между PARTITION BY и ORDER BY внутри клаузы OVER?
- 02Почему SUM(amount) OVER (PARTITION BY region ORDER BY sale_date) даёт накопительный итог вместо плоского итога региона?
- 03Нужны и плоский итог региона, и накопительный итог в одном запросе. Как избежать ловушки фрейма?
Окно формируется двумя клаузами внутри OVER (...). PARTITION BY пространственный: он нарезает строки на независимые окна, сбрасывающиеся на каждой границе, так что функция всегда видит лишь строки своей партиции — то же ограничение, что делает GROUP BY, минус схлопывание. ORDER BY внутри OVER временной: он упорядочивает строки внутри каждой партиции, чтобы функции могли считать «до сюда», «предыдущая строка» или «ранг на текущий момент». Ловушка в том, что этот внутренний ORDER BY — не просто сортировка: он тихо меняет фрейм по умолчанию со всей партиции на RANGE UNBOUNDED PRECEDING .. CURRENT ROW, переворачивая плоский агрегат в накопительный без всякой ошибки. Защита — намеренные окна: опускай внутренний ORDER BY для плоских итогов, добавляй только для накопительных и именуй окна, когда запросу нужны оба. Теперь, когда видишь оконный запрос, возвращающий накопительный итог вместо ожидаемого плоского, — первым делом проверяй внутренний ORDER BY: в девяти случаях из десяти виновник там. Следующий урок делает фреймы полностью явными — ROWS против RANGE против GROUPS и синтаксис границ — чтобы ты точно контролировал, какие строки агрегирует каждая функция.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.