Вычисление нарастающего итога для N таблиц в рамках одного запроса

02.10.11

Разработка - Запросы

Данная часть статья является чисто теоретическим отступлением, объясняющим механизм, который будет использован в дальнейшем. В статье объясняется, как получить значения «нарастающего итога» для N различных таблиц в рамках одного запроса.

Для начала рассмотрим общий вид запроса, в котором присутствуют две (или более) таблицы. Первый вариант:

SELECT
 Таблица1.*,
 Таблица2.*
FROM Таблица1, Таблица2

Второй вариант (по итогу этот вариант аналогичен первому):

SELECT
 *
FROM Таблица1
LEFT JOIN Таблица2
ON Таблица1.ПолеСоединения = Таблица2.ПолеСоединения

Примем, что в качестве «Таблицы1» и «Таблицы2» используются таблицы со следующей структурой и значениями:

В наши цели входит одновременное вычисление «нарастающего итога» для этих двух таблиц одновременно, в рамках одного запроса. Напишем подобный запрос:

SELECT
 Таблица11.ПолеПодсчета1,
 SUM( Таблица12.ПолеСуммирования1 ) AS СуммаПредПолей1,
 Таблица21.ПолеПодсчета2,
 SUM( Таблица22.ПолеСуммирования2 ) AS СуммаПредПолей2
FROM Таблица1 AS Таблица11
LEFT JOIN Таблица1 AS Таблица12
ON Таблица11.ПолеПодсчета1 > Таблица12.ПолеПодсчета1,
Таблица2 AS Таблица21
LEFT JOIN Таблица2 AS Таблица22
ON Таблица21.ПолеПодсчета2 > Таблица22.ПолеПодсчета2
GROUP BY Таблица11.ПолеПодсчета1, Таблица21.ПолеПодсчета2

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

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

Значения в «Таблице1» и «Таблице2» подобраны так, чтобы при вычислении сумм давать последовательные значения (2, 3, 4 и т.д.). Однако, как можно заметить из последнего рисунка, значения не всегда соответствуют ожидаемым (см. выделенные области на рисунке). Для пояснения причин подобных «ошибок суммирования» приведем результат запроса к «Таблице1» и «Таблице2» без группировки значений и подсчета сумм:

SELECT
Таблица1.*,
Таблица2.*
FROM Таблица1, Таблица2

Как можно заметить, в полученной таблице для каждой строки из «Таблицы1» присутствуют все строки из «Таблицы2». Следовательно строки как из «Таблицы1», так и из «Таблицы2» просто повторяются, что и приводит в свою очередь к «неправильному суммированию».

Для решения проблемы «неправильного суммирования» выполним следующий запрос:

SELECT
 Таблица11.ПолеПодсчета1,
 COUNT( DISTINCT Таблица12.ПолеПодсчета1 ) AS Счетчик1,
 SUM( Таблица12.ПолеСуммирования1 ) AS СуммаПредПолей1,
 Таблица21.ПолеПодсчета2,
 COUNT( DISTINCT Таблица22.ПолеПодсчета2 ) AS Счетчик2,
 SUM( Таблица22.ПолеСуммирования2 ) AS СуммаПредПолей2
FROM Таблица1 AS Таблица11
LEFT JOIN Таблица1 AS Таблица12
ON Таблица11.ПолеПодсчета1 > Таблица12.ПолеПодсчета1,
Таблица2 AS Таблица21
LEFT JOIN Таблица2 AS Таблица22
ON Таблица21.ПолеПодсчета2 > Таблица22.ПолеПодсчета2
GROUP BY Таблица11.ПолеПодсчета1, Таблица21.ПолеПодсчета2

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

Как можно видеть из рисунка, имеется прямая связь между значениями счётчиков, и просуммированными значениями - суммированные значения превышают правильные данные ровно в то количество раз, которое и является значением соответствующего счётчика. Таким образом, правильное значение для сумм предыдущих значений суммируемой колонки можно получить, разделив первично вычисленное значение суммы на величину соответствующего счётчика. Один нюанс - счетчик может принимать значение «0», и в запросе следует предусмотреть этот вариант.

Запишем правильный запрос для вычисления полноценного «нарастающего итога» для двух различных таблиц:

SELECT
 Таблица11.ПолеПодсчета1,
 SUM( Таблица12.ПолеСуммирования1 ) 
 / 
 CASE
  WHEN
   COUNT( DISTINCT Таблица22.ПолеПодсчета2 ) = 0
  THEN
   1
 ELSE
  COUNT( DISTINCT Таблица22.ПолеПодсчета2 )
 END + MAX( Таблица11.ПолеСуммирования1 ) AS НарастающийИтог1,
 Таблица21.ПолеПодсчета2,
 SUM( Таблица22.ПолеСуммирования2 ) 
 / 
 CASE
  WHEN
   COUNT( DISTINCT Таблица12.ПолеПодсчета1 ) = 0
  THEN
   1
 ELSE
  COUNT( DISTINCT Таблица12.ПолеПодсчета1 )
 END + MAX( Таблица21.ПолеСуммирования2 ) AS НарастающийИтог2
FROM Таблица1 AS Таблица11
LEFT JOIN Таблица1 AS Таблица12
ON Таблица11.ПолеПодсчета1 > Таблица12.ПолеПодсчета1, 
Таблица2 AS Таблица21
LEFT JOIN Таблица2 AS Таблица22
ON Таблица21.ПолеПодсчета2 > Таблица22.ПолеПодсчета2
GROUP BY Таблица11.ПолеПодсчета1, Таблица21.ПолеПодсчета2

Теперь добавим в запрос третью таблицу со следующими значениями:

Опять выполним запрос, в котором будут объединены все три таблицы и будут присутствовать нужные нам значения счётчиков:

SELECT
 Таблица11.ПолеПодсчета1,
 COUNT( DISTINCT Таблица12.ПолеПодсчета1 ) AS Счетчик1,
 SUM( Таблица12.ПолеСуммирования1 ) AS СуммаПредпПолей1,
 Таблица21.ПолеПодсчета2,
 COUNT( DISTINCT Таблица22.ПолеПодсчета2 ) AS Счетчик2,
 SUM( Таблица22.ПолеСуммирования2 ) AS СммаПредпПолей2,
 Таблица31.ПолеПодсчета3,
 COUNT( DISTINCT Таблица32.ПолеПодсчета3 ) AS Счетчик3,
 SUM( Таблица32.ПолеСуммирования3 ) AS СуммаПредпПолей3
FROM Таблица1 AS Таблица11
LEFT JOIN Таблица1 AS Таблица12
ON Таблица11.ПолеПодсчета1 > Таблица12.ПолеПодсчета1, 
Таблица2 AS Таблица21
LEFT JOIN Таблица2 AS Таблица22
ON Таблица21.ПолеПодсчета2 > Таблица22.ПолеПодсчета2,
Таблица3 AS Таблица31
LEFT JOIN Таблица3 AS Таблица32
ON Таблица31.ПолеПодсчета3 > Таблица32.ПолеПодсчета3
GROUP BY Таблица11.ПолеПодсчета1, Таблица21.ПолеПодсчета2, Таблица31.ПолеПодсчета3

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

Приведем запрос, позволяющий вычислить «нарастающие итоги» для трех таблиц одновременно:

SELECT
 Таблица11.ПолеПодсчета1,
 SUM( Таблица12.ПолеСуммирования1 ) 
 / 
 (
  CASE
   WHEN
    COUNT( DISTINCT Таблица22.ПолеПодсчета2 ) = 0
   THEN
    1
  ELSE
   COUNT( DISTINCT Таблица22.ПолеПодсчета2 )
  END 
  *
  CASE
   WHEN
    COUNT( DISTINCT Таблица32.ПолеПодсчета3 ) = 0
   THEN
    1
  ELSE
   COUNT( DISTINCT Таблица32.ПолеПодсчета3 )
  END 
 ) + MAX( Таблица11.ПолеСуммирования1 ) AS НарастающийИтог1,
 Таблица21.ПолеПодсчета2,
 SUM( Таблица22.ПолеСуммирования2 ) 
 / 
 (
  CASE
   WHEN
    COUNT( DISTINCT Таблица12.ПолеПодсчета1 ) = 0
   THEN
    1
  ELSE
   COUNT( DISTINCT Таблица12.ПолеПодсчета1 )
  END
  *
  CASE
   WHEN
    COUNT( DISTINCT Таблица32.ПолеПодсчета3 ) = 0
   THEN
    1
  ELSE
   COUNT( DISTINCT Таблица32.ПолеПодсчета3 )
  END 
 ) + MAX( Таблица21.ПолеСуммирования2 ) AS НарастающийИтог2,
 Таблица31.ПолеПодсчета3,
 SUM( Таблица32.ПолеСуммирования3 )
 /
 (
  CASE
   WHEN
    COUNT( DISTINCT Таблица12.ПолеПодсчета1 ) = 0
   THEN
    1
  ELSE
   COUNT( DISTINCT Таблица12.ПолеПодсчета1 )
  END
  *
  CASE
   WHEN
    COUNT( DISTINCT Таблица22.ПолеПодсчета2 ) = 0
   THEN
    1
  ELSE
   COUNT( DISTINCT Таблица22.ПолеПодсчета2 )
  END
 ) + MAX( Таблица31.ПолеСуммирования3 ) AS НарастающийИтог3
FROM Таблица1 AS Таблица11
LEFT JOIN Таблица1 AS Таблица12
ON Таблица11.ПолеПодсчета1 > Таблица12.ПолеПодсчета1, 
Таблица2 AS Таблица21
LEFT JOIN Таблица2 AS Таблица22
ON Таблица21.ПолеПодсчета2 > Таблица22.ПолеПодсчета2,
Таблица3 AS Таблица31
LEFT JOIN Таблица3 AS Таблица32
ON Таблица31.ПолеПодсчета3 > Таблица32.ПолеПодсчета3
GROUP BY Таблица11.ПолеПодсчета1, Таблица21.ПолеПодсчета2, Таблица31.ПолеПодсчета3

В общем случае, когда число соединяемых таблиц в одном запросе равно N, в качестве делителя для сумм следует использовать произведение счетчиков для всех других таблиц. В общем случае множитель для сумм таблицы с номером «i» будет выражаться формулой:

 

С уважением,

Юрий Строжевский

 

 

См. также

Инструментарий разработчика Роли и права Запросы СКД Программист Руководитель проекта Платформа 1С v8.3 Управляемые формы Запросы Система компоновки данных Платные (руб)

Инструменты для разработчиков 1С 8.3: Infostart Toolkit. Автоматизация и ускорение разработки на управляемых формах. Легкость работы с 1С.

12000 руб.

02.09.2020    171777    962    403    

924

Запросы Программист Бесплатно (free)

Увидел cheatsheet по SQL и захотелось нарисовать подобное, но про запросы.

18.10.2024    11719    sergey279    18    

65

Запросы Программист Платформа 1С v8.3 Запросы 1C:Бухгалтерия Бесплатно (free)

Столкнулся с интересной ситуацией, которую хотел бы разобрать, ввиду её неочевидности. Речь пойдёт про использование функции запроса АВТОНОМЕРЗАПИСИ() и проблемы, которые могут возникнуть.

11.10.2024    6674    XilDen    36    

83

Запросы Программист Запросы Бесплатно (free)

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

16.08.2024    9364    user1840182    5    

28

Математика и алгоритмы Запросы Программист Платформа 1С v8.3 Запросы Бесплатно (free)

Рассмотрим быстрый алгоритм поиска дублей с использованием hash функции по набору полей шапки и табличных частей.

08.07.2024    2805    ivanov660    9    

22

Запросы СКД Программист Стажер Система компоновки данных Россия Бесплатно (free)

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

15.05.2024    10669    implecs_team    6    

48

Запросы Программист Стажер Платформа 1С v8.3 1C:Бухгалтерия Бесплатно (free)

Часто поступают задачи по произвольному распределению общих сумм. После распределения иногда пропадают копейки. Суть решения добавить АвтоНомерЗаписи() в ВТ распределения, и далее используя функции МАКСИМУМ или МИНИМУМ можем положить разницу копеек в первую или последнюю строку знаменателя распределения.

11.04.2024    3673    andrey_sag    10    

38
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. fishca 1259 03.10.11 09:24 Сейчас в теме
Было бы очень неплохо сравнить с функцией СКД, или это не одно и то же?

ВЫЧИСЛИТЬВЫРАЖЕНИЕ()

Синтаксис:

ВычислитьВыражение(<Выражение>, <Группировка>, <ОбластьВычисления>, <Начало>, <Конец>, <Сортировка>, <ИерархическаяСортировка>, <ОбработкаОдинаковыхЗначенийПорядка>)

Описание:

Функция предназначена для вычисления выражения в контексте некоторой группировки.
2. anig99 2853 03.10.11 09:55 Сейчас в теме
(1) немного разные вещи. Тут всё-таки чистый запрос и ВычислитьВыражение недавно появилось.
3. fishca 1259 03.10.11 10:26 Сейчас в теме
(2) Понятно что немного разные. Интересно скорость обработки будет та же или нет?
4. anig99 2853 03.10.11 10:45 Сейчас в теме
(3) на маленьких объемах данных скорее всего выиграет запрос - нет потерь на передачах данных. В СКД ВычислитьВыражение реализовано не на уровне SQL, а на уровне платформы.
На больших объемах неоптимизированный запрос затухнет в силу много раз проговоренного лавинообразно растущего объема данных.
5. y-str 60 03.10.11 11:18 Сейчас в теме
(4) Любая оценка скорости/требовательности к ресурсам нуждается в подтверждении экспериментальными данными.
6. anig99 2853 03.10.11 14:33 Сейчас в теме
(5) мы их как-то проводили, просто на форуме где-то потерялись. Буду переделывать свой отчет по дебиторке, выложу все 3 сравнения. Код на сервере, неоптимизированный запрос, оптимизированный запрос, запрос на СКД с функцией ВычислитьВыражение.
7. y-str 60 03.10.11 14:47 Сейчас в теме
(6) Отлично, будет здорово посмотреть результаты!
8. Qseft 05.10.11 16:08 Сейчас в теме
Примерно когда ожидать результатов сравнения? Очень интересно.
Оставьте свое сообщение