← Все курсы · Курс SQL · Все руководства
Подсчет итогов в SQL: оконный фрейм, который определяет ответ
В конце этой страницы вы можете добавить накопительный столбец к любому упорядоченному результату, написать предложение фрейма, в котором точно указано, какие именно строки добавляются, выполнить отдельные итоги для каждой группы за один проход, превратить одну и ту же фигуру в скользящее среднее и проверить все это одним сравнением. Это около двадцати пяти минут, и все приведенные ниже результаты были просчитаны.
Вот чем заняться сегодня. Найдите запрос промежуточной суммы и найдите слова ROWS BETWEEN. Если их нет, вы используете фрейм по умолчанию, и в любом столбце с повторяющимися значениями фрейм по умолчанию дает каждой связанной строке один и тот же совокупный показатель. Добавлять ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW и сравните их.
Краткая версия: SUM(x) OVER (ORDER BY y) складывает рамка строк, которая заканчивается на текущей, а предложение фрейма определяет, где она заканчивается.
Идея заключается в том, чтобы кадр увеличивался по одной строке за раз, поэтому он передает изображение.
- Промежуточная сумма в одну строку
- Что на самом деле представляет собой рамка
- ORDER BY — вот что заставляет его работать
- Связи и фрейм по умолчанию, объединяющий их
- Отдельный промежуточный итог на группу
- Рамка, которая скользит: скользящие средние
- Проверка, которая фиксирует все
- Текущий подсчет и текущая доля
- Полное до и после
- Пограничные случаи, которые нарушают промежуточный итог
- Почему это работает
- Использование этого в вашем собственном проекте
- Все на одном экране
1. Однострочный промежуточный итог
Сначала суммируйте уровень детализации, которое хотите накопить. Здесь это одна строка в месяц, которая является той же отправной точкой, что и любая работа с трендами, и рассматривается в группировка по месяцам.
2026-01-01 | 2630
2026-02-01 | 2245
2026-03-01 | 2585
2026-05-01 | 2430
Теперь накопительный столбец.
SELECT month, revenue,
SUM(revenue) OVER (ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running
FROM by_month
ORDER BY month;
2026-01-01 | 2630 | 2630
2026-02-01 | 2245 | 4875
2026-03-01 | 2585 | 7460
2026-05-01 | 2430 | 9890
Проверьте вручную: 2630 плюс 2245 — это 4875, плюс 2585 — это 7460, плюс 2430 — это 9890, что составляет весь доход таблицы. Это последнее равенство не является украшением; это испытание, и седьмой раздел делает его привычкой.
Прочтите этот пункт как предложение: суммируйте доход по строкам, упорядоченным по месяцам, с самого начала до этой строки включительно. Вот что UNBOUNDED PRECEDING и CURRENT ROW имею в виду, и как только вы сможете произнести это вслух, синтаксис перестанет нуждаться в поиске.
2. Что на самом деле представляет собой фрейм
До пояснения: нормальный SUM с GROUP BY сворачивает строки. Этот сохраняет все четыре. Скажите, откуда взялось дополнительное число.
Из рамка: набор строк, определенный относительно строки, в которой вы находитесь в данный момент. Оконная функция один раз обрабатывает результат и в каждой строке просматривает диапазон соседних строк и что-то вычисляет над ними. Сама строка никогда не сворачивается, поэтому из шестнадцати строк получается шестнадцать.
Три части по порядку, и все три необязательны, вот отсюда и беда.
| Часть | Что он решает | Если вы оставите это |
|---|---|---|
PARTITION BY | Где возобновляется накопление | Один раздел: весь результат |
ORDER BY | Последовательность перемещения кадра | Никакой рамки вообще; каждая строка видит все |
ROWS BETWEEN ... | Какие именно строки охватывает кадр | Значение по умолчанию, отличное от того, которое вы имели в виду |
Остальная часть страницы — это те три, по одному. Если вы хотите более широкий тур о том, что еще OVER можно сделать, ранжирование и LAG и остальное, руководство по функциям окна это место.
3. ORDER BY — вот что заставляет его работать
Перед объяснением: удалите ORDER BY изнутри OVER. Предскажите четыре числа.
SELECT month, revenue, SUM(revenue) OVER () AS whole_thing
FROM by_month ORDER BY month;
2026-01-01 | 2630 | 9890
2026-02-01 | 2245 | 9890
2026-03-01 | 2585 | 9890
2026-05-01 | 2430 | 9890
Общий итог, повторенный четыре раза. Без ORDER BY здесь нет последовательности, поэтому нет «пока», и кадр каждой строки представляет собой весь раздел. Это действительно полезная вещь: именно так вы ставите знаменатель рядом с каждой строкой, и это не промежуточный итог.
Обратите внимание также на ORDER BY в конце утверждения совсем другое. Он сортирует вывод. Тот, что внутри OVER определяет порядок накопления. Часто это один и тот же столбец, и это не обязательно, и запрос, который сортирует выходные данные по одному столбцу и накапливает данные по другому, создаст накопительный столбец, который кажется скачущим.
4. Связи и фрейм по умолчанию, объединяющий их
Это раздел, который оправдывает страницу. Упорядочивайте по доходам, а не по месяцам, чтобы существовали связи: два заказа стоят 440, два — 660 и два — 880. Вычислите промежуточную сумму дважды: один раз с фреймом по умолчанию и один раз с ROWS.
order_id | revenue | default | ROWS
1004 | 240 | 240 | 240
1009 | 280 | 520 | 520
1007 | 425 | 945 | 945
1011 | 440 | 1825 | 1385
1016 | 440 | 1825 | 1825
1008 | 480 | 2305 | 2305
1014 | 510 | 2815 | 2815
1013 | 600 | 3415 | 3415
1003 | 660 | 4735 | 4075
1005 | 660 | 4735 | 4735
1006 | 680 | 5415 | 5415
1012 | 765 | 6180 | 6180
1002 | 850 | 7030 | 7030
1001 | 880 | 8790 | 7910
1015 | 880 | 8790 | 8790
1010 | 1100 | 9890 | 9890
Посмотрите на две строки 440. По умолчанию для обоих указано 1825. ROWS говорит 1385, а затем 1825. То же самое касается двух 660-х и двух 880-х: шесть из шестнадцати строк не совпадают.
Это тоже не ошибка. Когда ты пишешь ORDER BY в окне и оставьте рамку вне поля, стандартное значение по умолчанию: RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, и RANGE Работает над Значения а не позиции: текущая строка означает каждую строку с тем же значением порядка, что и эта. Таким образом, оба числа 440 находятся внутри кадра друг друга, и оба получают сумму, включая оба. ROWS работает с позициями, поэтому текущая строка означает буквально эту строку и не далее.
Скажите вслух, какой из них вам нужен для сводного отчета. Почти всегда ROWS, потому что читатель, просматривающий промежуточную сумму, ожидает, что каждая строка добавит свое значение и не более того. RANGE является правильным ответом, когда связанные строки действительно принадлежат друг другу, например, совокупный итог по дням, когда несколько заказов имеют общую дату, и вы хотите, чтобы в строках каждого дня отображались цифры закрытия дня.
Причина, по которой никто этого не замечает, заключается в том, что в уникальном столбце заказа они идентичны, поэтому запрос месяца в первом разделе в любом случае дал один и тот же ответ. Закажите что-нибудь с повторами, и они расходятся. Введите рамку.
5. Отдельный промежуточный итог на группу.
PARTITION BY перезапускает накопление при каждом изменении столбца раздела.
SELECT region, order_date, units*unit_price AS revenue,
SUM(units*unit_price) OVER (PARTITION BY region ORDER BY order_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running
FROM orders ORDER BY region, order_date;
East | 2026-01-19 | 660 | 660
East | 2026-02-09 | 680 | 1340
East | 2026-03-09 | 1100 | 2440
East | 2026-05-04 | 600 | 3040
North | 2026-01-05 | 880 | 880
North | 2026-01-26 | 240 | 1120
North | 2026-02-16 | 425 | 1545
North | 2026-03-16 | 440 | 1985
North | 2026-05-11 | 510 | 2495
...
Каждый регион начинает заново со своего первого порядка, и последнее текущее значение каждого региона равно общему значению этого региона: Восток 3040, Север 2495, Юг 2670, Запад 1685. Эти четыре в сумме дают 9890, поэтому разделы между ними составляют каждую строку.
Это та же идея раздела, что и GROUP BYс одним отличием, о котором стоит сказать прямо: GROUP BY заменяет строки одной строкой на группу и PARTITION BY сохраняет каждую строку и добавляет столбец. Если вы хотите, чтобы и деталь, и групповая фигура находились на одной линии, это единственный из двух, который может это сделать.
6. Скользящая рамка: скользящие средние
Измените место начала кадра, и он перестанет накапливаться и начнет скользить. ROWS BETWEEN 2 PRECEDING AND CURRENT ROW окно состоит из трех строк: этой строки и двух перед ней.
Выполните ежемесячный ряд с календарем, чтобы пустой апрель был настоящим нулем, а не отсутствующей строкой:
month | revenue | ma3 | running
2026-01-01 | 2630 | 2630.00 | 2630
2026-02-01 | 2245 | 2437.50 | 4875
2026-03-01 | 2585 | 2486.67 | 7460
2026-04-01 | 0 | 1610.00 | 7460
2026-05-01 | 2430 | 1671.67 | 9890
Проверьте первый: три месяца, заканчивающиеся в марте, составляют 2630, 2245 и 2585, что в среднем составляет 2486,67. Обратите внимание на первые две строки: рамка не может выйти за пределы начала, поэтому среднее значение января превышает одну строку, а среднее значение февраля — более двух. Обычно это нормально, но иногда вводит в заблуждение, и если это имеет значение, вы подавляете первые строки, а не позволяете среднему значению одной строки выглядеть как трехмесячное.
Текущий столбец, не перемещающийся между мартом и апрелем, — это календарь, выполняющий свою работу: месяц без продаж ничего не добавляет, и он виден как плоский шаг, а не как отсутствующая строка. Весь этот аргумент справочник по датам и это применимо к каждому фрейму на этой странице, поскольку в фрейме подсчитываются строки, а отсутствующий месяц — это отсутствующая строка.
Еще два кадра, которые стоит знать поименно. ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING накапливается отсюда до конца, что дает столбец остатка. И ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING центрирует окно по текущей строке, что, честно говоря, более плавно, когда вы не делаете прогнозы.
7. Проверка, которая ловит всё
Одно сравнение сразу выявляет ошибку фрейма, неправильный раздел, отсутствующую строку и дублирующуюся строку.
last running value | grand total
9890 | 9890
Окончательное значение накопительного столбца должно равняться простой сумме столбца, который он накапливает. Если оно выше, что-то учитывается дважды, обычно соединение, умножающее строки. Если оно меньше, строки пропадают еще до того, как окно их увидит, обычно это фильтр или внутреннее соединение. Если они равны, но промежуточные значения выглядят неправильными, подозрительным является кадр.
Запишите это как запрос, а не делайте это на глаз, потому что «последнее число выглядит правильно» не является проверкой:
SELECT (SELECT MAX(running) FROM r) AS last_running,
(SELECT SUM(units*unit_price) FROM orders) AS grand_total;
Представьте себе сводный отчет, который вы отправляете чаще всего. Есть ли в нем что-нибудь, что могло бы подсказать вам, если бы его последняя строка и его итоговая сумма перестали совпадать?
8. Текущий подсчет и текущая доля
Одна и та же форма работает со всем, что можно агрегировать, и два варианта занимают свое место практически в любом сводном отчете.
month | orders | running_orders | pct_of_year
2026-01-01 | 4 | 4 | 26.6
2026-02-01 | 4 | 8 | 49.3
2026-03-01 | 4 | 12 | 75.4
2026-05-01 | 4 | 16 | 100.0
Текущий счетчик отвечает на вопрос «сколько уже» и заканчивается на 16, что соответствует количеству строк в таблице. Столбец «Доля» делит промежуточную сумму на общую сумму, и люди на самом деле его читают, потому что 75,4% годового дохода к марту — это предложение, а 7460 — число.
Обратите внимание, как была построена доля: оконная промежуточная сумма, разделенная на окно без ORDER BY, что из третьего раздела представляет собой сумму всего раздела. Два окна в одном выражении, одно накапливающее, другое нет.
ROUND(100.0 * SUM(revenue) OVER (ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
/ SUM(revenue) OVER (), 1)
И последняя строка должна быть 100,0. Если это не так, то проверка седьмого раздела не удалась под другим видом.
Полное до и после
Тот же вопрос: как накапливалась выручка в течение года и какая доля приходилась на каждый месяц?
До
SELECT month, revenue,
SUM(revenue) OVER (ORDER BY revenue) AS running
FROM by_month;
Три проблемы в одной строке. Накопление упорядочено по доходам, а не по месяцам, поэтому столбец увеличивается по размеру и ничего не значит. Рамочного положения нет, поэтому связанные доходы будут иметь одну и ту же цифру. И проверки нигде нет, поэтому ничего этого не видно.
после
SELECT month,
revenue,
SUM(revenue) OVER (ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running,
ROUND(100.0 * SUM(revenue) OVER (ORDER BY month
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
/ SUM(revenue) OVER (), 1) AS pct
FROM by_month
ORDER BY month;
2026-01-01 | 2630 | 2630 | 26.6
2026-02-01 | 2245 | 4875 | 49.3
2026-03-01 | 2585 | 7460 | 75.4
2026-05-01 | 2430 | 9890 | 100.0
Упорядочено по времени, явно оформлено и завершено столбцом общего доступа, последнее значение которого должно быть 100,0. Последняя текущая цифра — 9890, что соответствует общему числу таблицы, поэтому столбец выверяется.
Утверждение, и именно поэтому предложение фрейма стоит вводить каждый раз: в столбце со связями фрейм по умолчанию давал шести из шестнадцати строк совокупное значение, включающее строку под ними, и единственной видимой разницей между двумя запросами было предложение, которое большинство людей никогда не пишут.
Пограничные случаи, которые нарушают промежуточный итог
Шесть, которые стоит знать.
Неуникальный столбец заказа. Раздел четвертый. Если в столбце заказа есть связи и вы не написали фрейм, то ответ не тот, который вы описываете. Добавление тай-брейка в ORDER BY, такой как первичный ключ, также исправляет его и делает результат детерминированным.
NULL в столбце заказа. Они сортируются в один конец, и какой конец зависит от двигателя. Строка с отсутствующей датой может оказаться в начале накопительного столбца, не неся ничего, или в конце, не содержащая лота.
NULL в столбце значений. SUM пропускает их, поэтому промежуточный итог выравнивается для этих строк, а не содержит ошибок. Посчитайте их, прежде чем накапливать.
Отсутствующий период. Рамка окна считает ряды, поэтому месяц без рядов — это не ровный шаг, его просто нет, и 2 PRECEDING возвращается на три месяца вместо двух. Сначала создайте календарь.
Накапливается после соединения, в результате которого умножаются строки. Текущая сумма превышает реальную сумму, и проверка седьмого раздела — единственное, что может это показать на странице.
Фильтрация по столбцу окна. WHERE running > 5000 не работает, поскольку оконные функции вычисляются после WHERE. Оберните запрос в CTE и отфильтруйте его вне него, что и является шаблоном. именованные шаги существовать для.
Почему это работает
Первое, что нужно знать, это то, что реляционная таблица не имеет внутреннего порядка. Строки являются членами набора, а у набора нет первого элемента, поэтому ORDER BY существует как шаг представления в самом конце запроса, а не как свойство таблицы (Кодд, 1970, Коммуникации ACM, 13(6), 377–387). Промежуточная сумма по своей сути связана с последовательностью, поэтому ее нельзя вычислить до тех пор, пока не будет предоставлена последовательность, и это именно то, что ORDER BY внутри OVER это для. Это не сортировка вывода; именно производство порядка делает «пока» значимым. Результат третьего раздела, общая сумма, повторенная четыре раза, — это то, что делает язык, когда вы запрашиваете накопление, не говоря, что именно.
Рамка – это вторая половина той же идеи. Как только заказ есть, движку все равно нужно сообщить, какую его часть видит каждая строка, и эти два варианта различаются, поскольку отвечают на разные вещи: ROWS подсчитывает позиции и RANGE сравнивает значения. Работа базы данных по оценке этих функций рассматривает фрейм как центральный объект, поскольку он определяет как ответ, так и то, насколько дешево ответ может быть получен за один упорядоченный проход (Цао, Чан, Ли и Тан, 2012, Труды Фонда ВЛДБ, 5(11), 1244–1255). Вот почему вообще существует значение по умолчанию и почему оно RANGE: это поведение, определенное стандартом, а не оплошность, и это удивительно только в том случае, если вы не знали, что был сделан выбор.
Одно замечание в шпаргалке ниже. Он создан для того, чтобы его просматривали и вспоминали, а не перечитывали, потому что активное извлечение чего-либо делает больше, чем просто фиксирует это в памяти, оно реорганизует то, что вы знаете, таким образом, что это переносит на новые проблемы (Карпике, 2012, Современные направления психологической науки, 21(3), 157–163).
Использование этого в вашем собственном проекте
Аудит каждой оконной функции одновременно – это утомительно. Вместо этого сделайте это по порядку.
- Найдите в своем SQL
OVER (ORDER BYи проверяйте каждое попадание на наличие предложения фрейма. Те, кого нет, - это очередь. - Для каждого из них спросите, есть ли повторы в столбце заказа. Если можно, добавьте
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROWи сравните до и после. - Добавьте тай-брейк в ORDER BY поэтому результат при каждом запуске один и тот же, даже на движке, который перемешивает одинаковые строки.
- Поместите сверку в запрос: последнее текущее значение по отношению к простому итогу в виде двух столбцов, которые вы можете видеть.
- Создайте календарь перед рамкой на чем-либо периодическом, поэтому пробел — это нулевая строка, а не отсутствующая.
- Переместите любой фильтр в столбце с окном наружу., в запрос, считывающий указанный шаг.
Если у вас под рукой есть бумага, один дополнительный эскиз стоит пяти минут. Нарисуйте строки в виде столбца прямоугольников, а рядом с ними нарисуйте скобку, которая вам действительно нужна в четвертой строке: где она начинается, где заканчивается и включает ли она двойника четвертой строки, если он есть. Затем напишите предложение фрейма, соответствующее скобкам. Большинство людей обнаруживают, что они представляли ROWS все время.
Все на одном экране
Это лист поиска. Закройте правую колонку, проработайте левую и произнесите каждый ответ вслух, прежде чем проверять его.
| вещь | Что он делает |
|---|---|
| Текущая сумма | SUM(x) OVER (ORDER BY y ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) |
| Что такое рамка | Строки относительно этой, которые складывает функция. |
| Нет ORDER BY внутри OVER | Никакой последовательности, поэтому каждая строка видит весь раздел. |
| Это все еще полезно | SUM(x) OVER () — знаменатель столбца «Доля». |
| Два ORDER BY | Внутри OVER задается накопление. В конце сортирует вывод. |
| Рамка по умолчанию | RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW. |
| RANGE | Сравнивает значения. Связанные ряды имеют одну фигуру. |
| ROWS | Считает позиции. Каждая строка добавляет только себя. |
| Что использовать | ROWS для сводного отчета. RANGE, когда связи принадлежат друг другу. |
| Почему никто не замечает | В уникальном столбце заказа они идентичны. |
PARTITION BY | Перезапускает накопление по группам. Сохраняет каждую строку. |
| Против GROUP BY | GROUP BY сворачивает строки; PARTITION BY добавляет столбец. |
| Скользящее среднее | ROWS BETWEEN 2 PRECEDING AND CURRENT ROW. |
| Короткие кадры в начале | Первые строки усредняют меньше значений. Подавите или назовите их. |
| Остаток | ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING. |
| Чек | Последнее текущее значение равно простой сумме. Доля заканчивается на уровне 100,0. |
| Фильтрация результата | Не в ГДЕ. Оберните его в CTE и отфильтруйте снаружи. |
Еще одна мысль, и мне бы искренне хотелось услышать ответы других людей. Мой отчет представлял собой сводный отчет о бронированиях, упорядоченный по стоимости сделки, а не по дате, и в течение квартала никто не подвергал сомнению этот отчет, потому что последнее число всегда было верным. Что незаметно сделала оконная рама в чем-то, что у вас есть?
Ссылки
- Кодд, EF (1970). Реляционная модель данных для крупных общих банков данных. Коммуникации ACM, 13(6), 377–387.
- Цао Ю., Чан К.-Ю., Ли Дж. и Тан К.-Л. (2012). Оптимизация аналитических оконных функций. Труды Фонда ВЛДБ, 5(11), 1244–1255.
- Карпике, JD (2012). Обучение, основанное на воспроизведении: активное воспроизведение способствует значимому обучению. Современные направления психологической науки, 21(3), 157–163.
SQL для аналитиков составляет 458 страниц, оконные рамы считывают по одному пункту за раз, поэтому промежуточная сумма учитывает то, что вы имели в виду.
SQL для аналитиков, 19 $ →тот Курс SQL дает вам повторы: совокупные столбцы для построения, ROWS против RANGE в одном и том же порядке, разделы для перезапуска и флэш-карты, точно указывающие, что означает каждая граница кадра. Теперь вы владеете методом; комплект - это практика. Ничего устанавливать, учетная запись не требуется.
Откройте набор SQL →