sql нарастающий итог по дням

Накопительные итоги стр. 1

В предположении некоторой упорядоченности строк накопительный итог для каждой строки представляет собой сумму значений некоторого числового столбца для этой строки и всех строк, расположенных выше данной.

Другими словами, накопительный итог для первой строки в упорядоченном наборе будет равен значению в этой строке. Для любой другой строки накопительный итог будет равен сумме значения в этой строке и накопительного итога в предыдущей строке.

Рассмотрим, например, такую задачу.

Для пункта 2 по таблице Outcome_o получить на каждый день суммарный расход за этот день и все предыдущие дни.

Вот запрос, который выводит информацию о расходах на пункте 2 в порядке возрастания даты

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

pointdateout
22001-03-22 00:00:00.0001440.00
22001-03-29 00:00:00.0007848.00
22001-04-02 00:00:00.0002040.00

Фактически, чтобы решить задачу нам нужно добавить еще один столбец, содержащий накопительный итог (run_tot). В соответствии с темой, этот столбец будет представлять собой коррелирующий подзапрос, в котором для ТОГО ЖЕ пункта, что и у ТЕКУЩЕЙ строки включающего запроса, и для всех дат, меньших либо равных дате ТЕКУЩЕЙ строки включающего запроса, будет подсчитываться сумма значений столбца out:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

pointdateoutrun_tot
22001-03-22 00:00:00.0001440.001440.00
22001-03-29 00:00:00.0007848.009288.00
22001-04-02 00:00:00.0002040.0011328.00

Собственно, использование пункта 2 продиктовано желанием уменьшить результирующую выборку. Чтобы получить накопительные итоги для каждого из пунктов, имеющихся в таблице Outcome_o, достаточно закомментировать строку

Ну а чтобы получить «сквозной» накопительный итог для всей таблицы нужно, видимо, убрать условие на равенство пунктов:

Однако при этом мы получим один и тот же накопительный итог для разных пунктов, работавших в один и тот же день. Вот подобный фрагмент из результирующей выборки,

pointdateoutrun_tot
12001-03-29 00:00:00.0002004.0033599.00
22001-03-29 00:00:00.0007848.0033599.00

Но это не проблема, если понять, что же мы хотим в итоге получить. Если нас интересует накопление расхода по дням, то нужно из выборки вообще исключить пункт и суммировать расходы по дням:

Источник

Накопительные итоги стр. 1

В предположении некоторой упорядоченности строк накопительный итог для каждой строки представляет собой сумму значений некоторого числового столбца для этой строки и всех строк, расположенных выше данной.

Другими словами, накопительный итог для первой строки в упорядоченном наборе будет равен значению в этой строке. Для любой другой строки накопительный итог будет равен сумме значения в этой строке и накопительного итога в предыдущей строке.

Рассмотрим, например, такую задачу.

Для пункта 2 по таблице Outcome_o получить на каждый день суммарный расход за этот день и все предыдущие дни.

Вот запрос, который выводит информацию о расходах на пункте 2 в порядке возрастания даты

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

pointdateout
22001-03-22 00:00:00.0001440.00
22001-03-29 00:00:00.0007848.00
22001-04-02 00:00:00.0002040.00

Фактически, чтобы решить задачу нам нужно добавить еще один столбец, содержащий накопительный итог (run_tot). В соответствии с темой, этот столбец будет представлять собой коррелирующий подзапрос, в котором для ТОГО ЖЕ пункта, что и у ТЕКУЩЕЙ строки включающего запроса, и для всех дат, меньших либо равных дате ТЕКУЩЕЙ строки включающего запроса, будет подсчитываться сумма значений столбца out:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

pointdateoutrun_tot
22001-03-22 00:00:00.0001440.001440.00
22001-03-29 00:00:00.0007848.009288.00
22001-04-02 00:00:00.0002040.0011328.00

Собственно, использование пункта 2 продиктовано желанием уменьшить результирующую выборку. Чтобы получить накопительные итоги для каждого из пунктов, имеющихся в таблице Outcome_o, достаточно закомментировать строку

Ну а чтобы получить «сквозной» накопительный итог для всей таблицы нужно, видимо, убрать условие на равенство пунктов:

Однако при этом мы получим один и тот же накопительный итог для разных пунктов, работавших в один и тот же день. Вот подобный фрагмент из результирующей выборки,

pointdateoutrun_tot
12001-03-29 00:00:00.0002004.0033599.00
22001-03-29 00:00:00.0007848.0033599.00

Но это не проблема, если понять, что же мы хотим в итоге получить. Если нас интересует накопление расхода по дням, то нужно из выборки вообще исключить пункт и суммировать расходы по дням:

Источник

Нарастающий итог в SQL

Нарастающий (накопительный) итог долго считался одним из вызовов SQL. Что удивительно, даже после появления оконных функций он продолжает быть пугалом (во всяком случае, для новичков). Сегодня мы рассмотрим механику 10 самых интересных решений этой задачи – от оконных функций до весьма специфических хаков.

В электронных таблицах вроде Excel нарастающий итог вычисляется очень просто: результат в первой записи совпадает с её значением:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

… а затем мы суммируем текущее значение и предыдущий итог.

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Появление в таблице двух и более групп несколько усложняет задачу: теперь мы считаем несколько итогов (для каждой группы отдельно). Впрочем, и здесь решение лежит на поверхности: необходимо каждый раз проверять, к какой группе принадлежит текущая запись. Click and drag, и работа выполнена:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Как можно заметить, подсчёт нарастающего итога связан с двумя неизменными составляющими:
(а) сортировкой данных по дате и
(б) обращением к предыдущей строке.

Но что SQL? Очень долго в нём не было нужного функционала. Необходимый инструмент – оконные функции – впервые появился только стандарте SQL:2003. К этому моменту они уже были в Oracle (версия 8i). А вот реализация в других СУБД задержалась на 5-10 лет: SQL Server 2012, MySQL 8.0.2 (2018 год), MariaDB 10.2.0 (2017 год), PostgreSQL 8.4 (2009 год), DB2 9 для z/OS (2007 год), и даже SQLite 3.25 (2018 год).

1. Оконные функции

Оконные функции – вероятно, самый простой способ. В базовом случае (таблица без групп) мы рассматриваем данные, отсортированные по дате:

… но нас интересуют только строки до текущей:

В конечном итоге, нам нужна сумма с этими параметрами:

А полный запрос будет выглядеть так:

В случае нарастающего итога по группам (поле grp ) нам требуется только одна небольшая правка. Теперь мы рассматриваем данные как разделённые на «окна» по признаку группы:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Чтобы учесть это разделение необходимо использовать ключевое слово partition by :

И, соответственно, считать сумму по этим окнам:

Тогда весь запрос преобразуется таким образом:

Производительность оконных функций будет зависеть от специфики вашей СУБД (и её версии!), размеров таблицы, и наличия индексов. Но в большинстве случаев этот метод будет самым эффективным. Тем не менее, оконные функции недоступны в старых версиях СУБД (которые ещё в ходу). Кроме того, их нет в таких СУБД как Microsoft Access и SAP/Sybase ASE. Если необходимо вендоро-независимое решение, следует обратить внимание на альтернативы.

2. Подзапрос

Как было сказано выше, оконные функции были очень поздно введены в основных СУБД. Эта задержка не должна удивлять: в реляционной теории данные не упорядочены. Куда больше духу реляционной теории соответствует решение через подзапрос.

Такой подзапрос должен считать сумму значений с датой до текущей (и включая текущую): sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням.

Что в коде выглядит так:

Чуть более эффективным будет решение, в котором подзапрос считает итог до текущей даты (но не включая её), а затем суммирует его со значением в строке:

В случае нарастающего итога по нескольким группам нам необходимо использовать коррелированный подзапрос:

Условие g.grp = t2.grp проверяет строки на вхождение в группу (что, в принципе, сходно с работой partition by grp в оконных функциях).

3. Внутреннее соединение

Поскольку подзапросы и джойны взаимозаменяемы, мы легко можем заменить одно на другое. Для этого необходимо использовать Self Join, соединив два экземпляра одной и той же таблицы:

Точно также можно сделать для случая с разными группами grp :

4. Декартово произведение

Раз уж мы заменили подзапрос на join, то почему бы не попробовать декартово произведение? Это решение потребует только минимальных правок:

Или для случая с группами:

Перечисленные решения (подзапрос, inner join, cartesian join) соответсвуют SQL-92 и SQL:1999, а потому будут доступны практически в любой СУБД. Основная проблема всех этих решений в низкой производительности. Это не велика беда, если мы материализуем таблицу с результатом (но ведь всё равно хочется большей скорости!). Дальнейшие методы куда более эффективны (с поправкой на уже указанные специфику конкретных СУБД и их версий, размер таблицы, индексы).

5. Рекурсивный запрос

Один из более специфических подходов – это рекурсивный запрос в common table expression. Для этого нам необходим «якорь» – запрос, возвращающий самую первую строку:

Часть кода, добавляющая один день, не универсальна. Например, это r.dt = dateadd(day, 1, cte.dt) для SQL Server, r.dt = cte.dt + 1 для Oracle, и т.д.

Совместив «якорь» и основной запрос, мы получим окончательный результат:

Решение для случая с группами будет ненамного сложнее:

6. Рекурсивный запрос с функцией row_number()

Итак, для рекурсивного запроса с row_number() нам понадобится два СТЕ. В первом мы только нумеруем строки:

… и если номер строки уже есть в таблице, то можно без него обойтись. В следующем запросе обращаемся уже к cte1 :

А целиком запрос выглядит так:

… или для случая с группами:

7. Оператор CROSS APPLY / LATERAL

Один из самых экзотических способов расчёта нарастающего итога – это использование оператора CROSS APPLY (SQL Server, Oracle) или эквивалентного ему LATERAL (MySQL, PostgreSQL). Эти операторы появились довольно поздно (например, в Oracle только с версии 12c). А в некоторых СУБД (например, MariaDB) их и вовсе нет. Поэтому это решение представляет чисто эстетический интерес.

Функционально использование CROSS APPLY или LATERAL идентично подзапросу: мы присоединяем к основному запросу результат вычисления:

… что целиком выглядит так:

Похожим будет и решение для случая с группами:

Итого: мы рассмотрели основные платформо-независимые решения. Но остаются решения, специфичные для конкретных СУБД! Поскольку здесь возможно очень много вариантов, остановимся на нескольких наиболее интересных.

8. Оператор MODEL (Oracle)

Оператор MODEL в Oracle даёт одно из самых элегантных решений. В начале статьи мы рассмотрели общую формулу нарастающего итога:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

MODEL позволяет реализовать эту формулу буквально один к одному! Для этого мы сначала заполняем поле total значениями текущей строки

Функция cv() здесь отвечает за значение текущей строки. А весь запрос будет выглядеть так:

9. Курсор (SQL Server)

Нарастающий итог – один из немногих случаев, когда курсор в SQL Server не только полезен, но и предпочтителен другим решениям (как минимум до версии 2012, где появились оконные функции).

Реализация через курсор довольно тривиальна. Сначала необходимо создать временную таблицу и заполнить её датами и значениями из основной:

Затем задаём локальные переменные, через которые будет происходить обновление:

После этого обновляем временную таблицу через курсор:

И, наконец, получем нужный результат:

10. Обновление через локальную переменную (SQL Server)

Обновление через локальную переменную в SQL Server основано на недокументированном поведении, поэтому его нельзя считать надёжным. Тем не менее, это едва ли не самое быстрое решение, и этим оно интересно.

Создадим две переменные: одну для нарастающих итогов и табличную переменную:

Сначала заполним @tv данным из основной таблицы

Затем табличную переменную @tv обновим, используя @VarTotal :

… после чего получим окончательный результат:

Резюме: мы рассмотрели топ 10 способов расчёта нарастающего итога в SQL. Как можно заметить, даже без оконных функций эта задача вполне решаема, причём механику решения нельзя назвать сложной.

Источник

Нарастающий итог в SQL

Нарастающий (накопительный) итог долго считался одним из вызовов SQL. Что удивительно, даже после появления оконных функций он продолжает быть пугалом (во всяком случае, для новичков). Сегодня мы рассмотрим механику 10 самых интересных решений этой задачи – от оконных функций до весьма специфических хаков.

В электронных таблицах вроде Excel нарастающий итог вычисляется очень просто: результат в первой записи совпадает с её значением:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

… а затем мы суммируем текущее значение и предыдущий итог.

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Появление в таблице двух и более групп несколько усложняет задачу: теперь мы считаем несколько итогов (для каждой группы отдельно). Впрочем, и здесь решение лежит на поверхности: необходимо каждый раз проверять, к какой группе принадлежит текущая запись. Click and drag, и работа выполнена:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Как можно заметить, подсчёт нарастающего итога связан с двумя неизменными составляющими:
(а) сортировкой данных по дате и
(б) обращением к предыдущей строке.

Но что SQL? Очень долго в нём не было нужного функционала. Необходимый инструмент – оконные функции – впервые появился только стандарте SQL:2003. К этому моменту они уже были в Oracle (версия 8i). А вот реализация в других СУБД задержалась на 5-10 лет: SQL Server 2012, MySQL 8.0.2 (2018 год), MariaDB 10.2.0 (2017 год), PostgreSQL 8.4 (2009 год), DB2 9 для z/OS (2007 год), и даже SQLite 3.25 (2018 год).

1. Оконные функции

Оконные функции – вероятно, самый простой способ. В базовом случае (таблица без групп) мы рассматриваем данные, отсортированные по дате:

… но нас интересуют только строки до текущей:

В конечном итоге, нам нужна сумма с этими параметрами:

А полный запрос будет выглядеть так:

В случае нарастающего итога по группам (поле grp ) нам требуется только одна небольшая правка. Теперь мы рассматриваем данные как разделённые на «окна» по признаку группы:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Чтобы учесть это разделение необходимо использовать ключевое слово partition by :

И, соответственно, считать сумму по этим окнам:

Тогда весь запрос преобразуется таким образом:

Производительность оконных функций будет зависеть от специфики вашей СУБД (и её версии!), размеров таблицы, и наличия индексов. Но в большинстве случаев этот метод будет самым эффективным. Тем не менее, оконные функции недоступны в старых версиях СУБД (которые ещё в ходу). Кроме того, их нет в таких СУБД как Microsoft Access и SAP/Sybase ASE. Если необходимо вендоро-независимое решение, следует обратить внимание на альтернативы.

2. Подзапрос

Как было сказано выше, оконные функции были очень поздно введены в основных СУБД. Эта задержка не должна удивлять: в реляционной теории данные не упорядочены. Куда больше духу реляционной теории соответствует решение через подзапрос.

Такой подзапрос должен считать сумму значений с датой до текущей (и включая текущую): sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням.

Что в коде выглядит так:

Чуть более эффективным будет решение, в котором подзапрос считает итог до текущей даты (но не включая её), а затем суммирует его со значением в строке:

В случае нарастающего итога по нескольким группам нам необходимо использовать коррелированный подзапрос:

Условие g.grp = t2.grp проверяет строки на вхождение в группу (что, в принципе, сходно с работой partition by grp в оконных функциях).

3. Внутреннее соединение

Поскольку подзапросы и джойны взаимозаменяемы, мы легко можем заменить одно на другое. Для этого необходимо использовать Self Join, соединив два экземпляра одной и той же таблицы:

Точно также можно сделать для случая с разными группами grp :

4. Декартово произведение

Раз уж мы заменили подзапрос на join, то почему бы не попробовать декартово произведение? Это решение потребует только минимальных правок:

Или для случая с группами:

Перечисленные решения (подзапрос, inner join, cartesian join) соответсвуют SQL-92 и SQL:1999, а потому будут доступны практически в любой СУБД. Основная проблема всех этих решений в низкой производительности. Это не велика беда, если мы материализуем таблицу с результатом (но ведь всё равно хочется большей скорости!). Дальнейшие методы куда более эффективны (с поправкой на уже указанные специфику конкретных СУБД и их версий, размер таблицы, индексы).

5. Рекурсивный запрос

Один из более специфических подходов – это рекурсивный запрос в common table expression. Для этого нам необходим «якорь» – запрос, возвращающий самую первую строку:

Часть кода, добавляющая один день, не универсальна. Например, это r.dt = dateadd(day, 1, cte.dt) для SQL Server, r.dt = cte.dt + 1 для Oracle, и т.д.

Совместив «якорь» и основной запрос, мы получим окончательный результат:

Решение для случая с группами будет ненамного сложнее:

6. Рекурсивный запрос с функцией row_number()

Итак, для рекурсивного запроса с row_number() нам понадобится два СТЕ. В первом мы только нумеруем строки:

… и если номер строки уже есть в таблице, то можно без него обойтись. В следующем запросе обращаемся уже к cte1 :

А целиком запрос выглядит так:

… или для случая с группами:

7. Оператор CROSS APPLY / LATERAL

Один из самых экзотических способов расчёта нарастающего итога – это использование оператора CROSS APPLY (SQL Server, SQL Server) или эквивалентного ему LATERAL (MySQL, PostgreSQL). Эти операторы появились довольно поздно (например, в Oracle только с версии 12c). А в некоторых СУБД (например, MariaDB) их и вовсе нет. Поэтому это решение представляет чисто эстетический интерес.

Функционально использование CROSS APPLY или LATERAL идентично подзапросу: мы присоединяем к основному запросу результат вычисления:

… что целиком выглядит так:

Похожим будет и решение для случая с группами:

Итого: мы рассмотрели основные платформо-независимые решения. Но остаются решения, специфичные для конкретных СУБД! Поскольку здесь возможно очень много вариантов, остановимся на нескольких наиболее интересных.

8. Оператор MODEL (Oracle)

Оператор MODEL в Oracle даёт одно из самых элегантных решений. В начале статьи мы рассмотрели общую формулу нарастающего итога:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

MODEL позволяет реализовать эту формулу буквально один к одному! Для этого мы сначала заполняем поле total значениями текущей строки

Функция cv() здесь отвечает за значение текущей строки. А весь запрос будет выглядеть так:

9. Курсор (SQL Server)

Нарастающий итог – один из немногих случаев, когда курсор в SQL Server не только полезен, но и предпочтителен другим решениям (как минимум до версии 2012, где появились оконные функции).

Реализация через курсор довольно тривиальна. Сначала необходимо создать временную таблицу и заполнить её датами и значениями из основной:

Затем задаём локальные переменные, через которые будет происходить обновление:

После этого обновляем временную таблицу через курсор:

И, наконец, получем нужный результат:

10. Обновление через локальную переменную (SQL Server)

Обновление через локальную переменную в SQL Server основано на недокументированном поведении, поэтому его нельзя считать надёжным. Тем не менее, это едва ли не самое быстрое решение, и этим оно интересно.

Создадим две переменные: одну для нарастающих итогов и табличную переменную:

Сначала заполним @tv данным из основной таблицы

Затем табличную переменную @tv обновим, используя @VarTotal :

… после чего получим окончательный результат:

Резюме: мы рассмотрели топ 10 способов расчёта нарастающего итога в SQL. Как можно заметить, даже без оконных функций эта задача вполне решаема, причём механику решения нельзя назвать сложной.

Источник

Нарастающий итог в SQL

Нарастающий (накопительный) итог долго считался одним из вызовов SQL. Что удивительно, даже после появления оконных функций он продолжает быть пугалом (во всяком случае, для новичков). Сегодня мы рассмотрим механику 10 самых интересных решений этой задачи – от оконных функций до весьма специфических хаков.

В электронных таблицах вроде Excel нарастающий итог вычисляется очень просто: результат в первой записи совпадает с её значением:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

… а затем мы суммируем текущее значение и предыдущий итог.

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Появление в таблице двух и более групп несколько усложняет задачу: теперь мы считаем несколько итогов (для каждой группы отдельно). Впрочем, и здесь решение лежит на поверхности: необходимо каждый раз проверять, к какой группе принадлежит текущая запись. Click and drag, и работа выполнена:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Как можно заметить, подсчёт нарастающего итога связан с двумя неизменными составляющими:
(а) сортировкой данных по дате и
(б) обращением к предыдущей строке.

Но что SQL? Очень долго в нём не было нужного функционала. Необходимый инструмент – оконные функции – впервые появился только стандарте SQL:2003. К этому моменту они уже были в Oracle (версия 8i). А вот реализация в других СУБД задержалась на 5-10 лет: SQL Server 2012, MySQL 8.0.2 (2018 год), MariaDB 10.2.0 (2017 год), PostgreSQL 8.4 (2009 год), DB2 9 для z/OS (2007 год), и даже SQLite 3.25 (2018 год).

1. Оконные функции

Оконные функции – вероятно, самый простой способ. В базовом случае (таблица без групп) мы рассматриваем данные, отсортированные по дате:

… но нас интересуют только строки до текущей:

В конечном итоге, нам нужна сумма с этими параметрами:

А полный запрос будет выглядеть так:

В случае нарастающего итога по группам (поле grp ) нам требуется только одна небольшая правка. Теперь мы рассматриваем данные как разделённые на «окна» по признаку группы:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

Чтобы учесть это разделение необходимо использовать ключевое слово partition by :

И, соответственно, считать сумму по этим окнам:

Тогда весь запрос преобразуется таким образом:

Производительность оконных функций будет зависеть от специфики вашей СУБД (и её версии!), размеров таблицы, и наличия индексов. Но в большинстве случаев этот метод будет самым эффективным. Тем не менее, оконные функции недоступны в старых версиях СУБД (которые ещё в ходу). Кроме того, их нет в таких СУБД как Microsoft Access и SAP/Sybase ASE. Если необходимо вендоро-независимое решение, следует обратить внимание на альтернативы.

2. Подзапрос

Как было сказано выше, оконные функции были очень поздно введены в основных СУБД. Эта задержка не должна удивлять: в реляционной теории данные не упорядочены. Куда больше духу реляционной теории соответствует решение через подзапрос.

Такой подзапрос должен считать сумму значений с датой до текущей (и включая текущую): sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням.

Что в коде выглядит так:

Чуть более эффективным будет решение, в котором подзапрос считает итог до текущей даты (но не включая её), а затем суммирует его со значением в строке:

В случае нарастающего итога по нескольким группам нам необходимо использовать коррелированный подзапрос:

Условие g.grp = t2.grp проверяет строки на вхождение в группу (что, в принципе, сходно с работой partition by grp в оконных функциях).

3. Внутреннее соединение

Поскольку подзапросы и джойны взаимозаменяемы, мы легко можем заменить одно на другое. Для этого необходимо использовать Self Join, соединив два экземпляра одной и той же таблицы:

Точно также можно сделать для случая с разными группами grp :

4. Декартово произведение

Раз уж мы заменили подзапрос на join, то почему бы не попробовать декартово произведение? Это решение потребует только минимальных правок:

Или для случая с группами:

Перечисленные решения (подзапрос, inner join, cartesian join) соответсвуют SQL-92 и SQL:1999, а потому будут доступны практически в любой СУБД. Основная проблема всех этих решений в низкой производительности. Это не велика беда, если мы материализуем таблицу с результатом (но ведь всё равно хочется большей скорости!). Дальнейшие методы куда более эффективны (с поправкой на уже указанные специфику конкретных СУБД и их версий, размер таблицы, индексы).

5. Рекурсивный запрос

Один из более специфических подходов – это рекурсивный запрос в common table expression. Для этого нам необходим «якорь» – запрос, возвращающий самую первую строку:

Часть кода, добавляющая один день, не универсальна. Например, это r.dt = dateadd(day, 1, cte.dt) для SQL Server, r.dt = cte.dt + 1 для Oracle, и т.д.

Совместив «якорь» и основной запрос, мы получим окончательный результат:

Решение для случая с группами будет ненамного сложнее:

6. Рекурсивный запрос с функцией row_number()

Итак, для рекурсивного запроса с row_number() нам понадобится два СТЕ. В первом мы только нумеруем строки:

… и если номер строки уже есть в таблице, то можно без него обойтись. В следующем запросе обращаемся уже к cte1 :

А целиком запрос выглядит так:

… или для случая с группами:

7. Оператор CROSS APPLY / LATERAL

Один из самых экзотических способов расчёта нарастающего итога – это использование оператора CROSS APPLY (SQL Server, Oracle) или эквивалентного ему LATERAL (MySQL, PostgreSQL). Эти операторы появились довольно поздно (например, в Oracle только с версии 12c). А в некоторых СУБД (например, MariaDB) их и вовсе нет. Поэтому это решение представляет чисто эстетический интерес.

Функционально использование CROSS APPLY или LATERAL идентично подзапросу: мы присоединяем к основному запросу результат вычисления:

… что целиком выглядит так:

Похожим будет и решение для случая с группами:

Итого: мы рассмотрели основные платформо-независимые решения. Но остаются решения, специфичные для конкретных СУБД! Поскольку здесь возможно очень много вариантов, остановимся на нескольких наиболее интересных.

8. Оператор MODEL (Oracle)

Оператор MODEL в Oracle даёт одно из самых элегантных решений. В начале статьи мы рассмотрели общую формулу нарастающего итога:

sql нарастающий итог по дням. Смотреть фото sql нарастающий итог по дням. Смотреть картинку sql нарастающий итог по дням. Картинка про sql нарастающий итог по дням. Фото sql нарастающий итог по дням

MODEL позволяет реализовать эту формулу буквально один к одному! Для этого мы сначала заполняем поле total значениями текущей строки

Функция cv() здесь отвечает за значение текущей строки. А весь запрос будет выглядеть так:

9. Курсор (SQL Server)

Нарастающий итог – один из немногих случаев, когда курсор в SQL Server не только полезен, но и предпочтителен другим решениям (как минимум до версии 2012, где появились оконные функции).

Реализация через курсор довольно тривиальна. Сначала необходимо создать временную таблицу и заполнить её датами и значениями из основной:

Затем задаём локальные переменные, через которые будет происходить обновление:

После этого обновляем временную таблицу через курсор:

И, наконец, получем нужный результат:

10. Обновление через локальную переменную (SQL Server)

Обновление через локальную переменную в SQL Server основано на недокументированном поведении, поэтому его нельзя считать надёжным. Тем не менее, это едва ли не самое быстрое решение, и этим оно интересно.

Создадим две переменные: одну для нарастающих итогов и табличную переменную:

Сначала заполним @tv данным из основной таблицы

Затем табличную переменную @tv обновим, используя @VarTotal :

… после чего получим окончательный результат:

Резюме: мы рассмотрели топ 10 способов расчёта нарастающего итога в SQL. Как можно заметить, даже без оконных функций эта задача вполне решаема, причём механику решения нельзя назвать сложной.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *