Оконная функция добавляет колонку, не схлопывая строки
GROUP BY сворачивает много строк в одну. Оконные функции SQL работают иначе: они считают значение по набору связанных строк, но сохраняют в результате каждую исходную строку. То есть вы получаете и детализацию по строкам, и агрегат - рядом, в одной выборке.
Синтаксис всегда один и тот же: функция, а после неё OVER (...).
Колонка total_all показывает общую сумму по всем строкам и повторяет её в каждой записи. Исходные строки при этом остаются нетронутыми. Сравните с SELECT SUM(amount) FROM sales - число получится то же, но в ответе будет всего одна строка. А оконные функции дают вам сразу оба представления.
PARTITION BY: агрегация внутри групп
Пустой OVER () считает агрегат по всей таблице. Если добавить PARTITION BY, агрегат будет считаться внутри групп - почти как GROUP BY, только без схлопывания строк.
Каждая строка получает итог по своему региону и долю в этом итоге. С обычным GROUP BY детализация по сотрудникам потерялась бы. В этом и состоит главный плюс оконных функций: детализация и агрегат в одном запросе.
Ранжирование: ROW_NUMBER, RANK, DENSE_RANK
Семейство ранжирующих функций нумерует строки в соответствии с ORDER BY внутри OVER. Три варианта отличаются тем, как они обрабатывают одинаковые значения.
Если посмотреть на результат:
ROW_NUMBER()всегда даёт уникальный номер - при равных значениях порядок определяется произвольно. Используйте эту функцию, когда нужен стабильный сквозной номер для каждой строки.RANK()присваивает одинаковый ранг строкам с равными значениями, но затем пропускает следующие номера. Если два игрока делят 1-е место, дальше идёт сразу 3-е.DENSE_RANK()тоже даёт одинаковый ранг при совпадениях, но номера не пропускает - следующим будет 2-е место.
Чтобы получить «топ-N в каждой группе», совместите ранжирование с PARTITION BY и отфильтруйте результат во внешнем запросе - напрямую в WHERE оконные функции использовать нельзя:
Топ-2 сотрудника по зарплате в каждом регионе.
LAG и LEAD: заглядываем в соседние строки
LAG(col) возвращает значение col из предыдущей строки окна, а LEAD(col) - из следующей. Обе функции отлично подходят для задач, где нужно сравнить значение с тем, что было раньше или будет дальше.
У первой строки yesterday будет NULL - до неё ничего нет. Можно подставить значение по умолчанию: запись LAG(celsius, 1, celsius) OVER (ORDER BY day) подставит сегодняшнее значение, если предыдущей строки не существует.
LEAD работает зеркально. Если скомбинировать обе функции с PARTITION BY, получится последовательность по каждой группе - например, сравнение продаж текущего месяца с предыдущим в рамках каждого региона.
Накопительная сумма через оконные рамки
Добавьте ORDER BY внутрь OVER - и агрегатные функции вроде SUM, AVG, COUNT начнут считаться нарастающим итогом:
Здесь стоит обратить внимание на две вещи:
SUM(amount) OVER (ORDER BY day)- это накопительная сумма. Когда вы пишетеORDER BYбез явного указания рамки, по умолчанию берётся диапазон «от начала окна до текущей строки».- Во втором столбце рамка задана явно:
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW. Это скользящее окно из трёх строк - то есть скользящее среднее.
Как это удобно держать в голове: любая оконная функция считается по рамке строк, заданной относительно текущей строки. Самые ходовые рамки:
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW- накопительный итог (та самая рамка по умолчанию).ROWS BETWEEN N PRECEDING AND CURRENT ROW- окно из последних N строк.ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING- вся партиция целиком.
ROWS отсчитывает физические строки. Есть ещё RANGE - он группирует по значению и пригождается, когда в столбце из ORDER BY встречаются одинаковые значения и вы хотите, чтобы они считались за один шаг.
FIRST_VALUE, LAST_VALUE и NTILE
Ещё несколько оконных функций, которые полезно знать:
FIRST_VALUEиLAST_VALUEвозвращают первое или последнее значение в рамке. СLAST_VALUEбудьте внимательны к рамке: по умолчанию она заканчивается наCURRENT ROW, поэтому, чтобы получить реальное последнее значение секции, обычно нужно явно указатьROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING.NTILE(n)делит строки наnпримерно равных групп - удобно для квартилей, перцентилей и разбиений в духе A/B-тестов.
Именованные окна через WINDOW
Когда несколько столбцов используют один и тот же OVER (...), повторять его становится утомительно. SQLite позволяет один раз задать окну имя и переиспользовать его:
Тот же запрос, только без визуального шума. Блок WINDOW ставится после WHERE/GROUP BY/HAVING и перед ORDER BY.
Оконные функции vs GROUP BY
Обе конструкции связаны с агрегацией, но решают разные задачи:
GROUP BYсворачивает данные. На выходе - одна строка на группу. Используйте, когда нужна только сводка.- Оконные функции ничего не теряют. Каждая исходная строка остаётся на месте, к ней просто добавляются вычисленные колонки.
Если вы ловите себя на том, что делаете GROUP BY, а потом джойните агрегаты обратно к исходной таблице - это явный сигнал, что задачу можно решить одним запросом через оконную функцию.
Пара подводных камней
WHEREне видит оконные функции. Фильтрация выполняется раньше, чем считаются окна. Оберните запрос в подзапрос или CTE и фильтруйте уже на внешнем уровне.- Неявные рамки кусаются.
SUM(x) OVER (ORDER BY y)даёт накопительную сумму, потому что рамка по умолчанию -RANGE UNBOUNDED PRECEDING. Если нужна сумма по всей партиции, пишитеOVER (PARTITION BY ...)безORDER BYлибо задавайте рамку явно. LAST_VALUEудивляет всех с первого раза. При стандартной рамке, заканчивающейся текущей строкой, функция возвращает текущее значение, а не последнее в партиции. Рамку нужно переопределить.- Оконные функции работают только в SQLite 3.25+ (релиз 2018 года). В любой более-менее свежей сборке они есть, но в некоторых встраиваемых окружениях версии бывают древнее.
Дальше: генерируемые колонки
Оконные функции - это вычисления на этапе запроса. Следующая страница про вычисления на этапе хранения: генерируемые колонки, значение которых задаётся выражением и пересчитывается автоматически при изменении исходных данных.
Часто задаваемые вопросы
Что такое оконные функции в SQLite?
Оконные функции считают значение по набору строк, связанных с текущей строкой, но при этом не схлопывают результат, как это делает GROUP BY. К функциям вроде ROW_NUMBER(), RANK(), SUM() или LAG() дописывается клауза OVER (...), которая и задаёт само окно. Все исходные строки остаются в выдаче - просто появляется ещё одна вычисляемая колонка.
Чем RANK отличается от DENSE_RANK в SQLite?
Обе функции присваивают ранг по ORDER BY, но по-разному обрабатывают одинаковые значения. RANK() оставляет «дыры» после совпадений: если две строки делят первое место, следующая получит ранг 3. DENSE_RANK() идёт без пропусков - следующая строка будет с рангом 2. Берите DENSE_RANK(), когда нужна сплошная нумерация, и RANK(), когда разрыв в номерах несёт смысл.
Как посчитать накопительный итог в SQLite?
Используйте SUM(column) OVER (ORDER BY ...) вместе с фреймом окна. По умолчанию ORDER BY внутри OVER подразумевает фрейм RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - это и даёт накопительную сумму. Добавьте PARTITION BY, если нужно сбрасывать итог в начале каждой группы.