Оконная функция добавляет колонку, не схлопывая строки
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, если нужно сбрасывать итог в начале каждой группы.