Итоги в динамическом списке

17 февраля 2013

Предисловие

Платформа 1С:Предприятие 8.2 может работать с множеством записей в таблице базы данных  динамическим способом, то есть считывать данные порциями. Ранее в статьях мы рассматривали механизм динамических списков и методы оптимизации работы с ними.

Сегодня мы решим нестандартную задачу для динамических списков. Нам нужно будет подсчитать итог по реквизиту документа "Сумма" и вывести его в подвале списка. Аналогично подсчитать среднее значение для поля "Рейтинг" и также вывести в подвале динамического списка. Подсчет итоговых полей должен учитывать отбор, установленный пользователем в настройках списка документов.

Итоговые поля в динамическом списке

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

Реализация

И так, перейдем к решению задачи. Начнем с изменении формы, далее опишем алгоритм получения итоговых значений.

Форма и интерфейс

Для начала подготовим форму документа для отображения итоговых полей. Для этого добавим два строковых реквизита формы "Рейтинг" и "Сумма".

Добавлены реквизиты формы

 В данные реквизиты будут записываться итоговые значения по документам. 

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

Включаем отображение подвала для элемента формы динамического списка

 

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

Устанавливаем путь к данным подвала

Теперь нужно определиться по какому событию будет проиходить обновление итогов в подвале списка. Для удобства разработки добавим команду "Обновить" и соответствующий ей элемент формы на командную панель. При выполнении этой команды будет происходить обновление итогов.

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

Алгоритм

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

Сложное условие отбора с использованием групп

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

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

1. Получаем исходный запрос динамического списка.

Исходный запрос динамического списка

Как мы видим, запрос выбирает все реквизиты документа. Для небольшого усложнения добавил собственное поле "УровеньРейтинга", формируемое конструкцией "ВЫБОР".

2. Формируем текст условий запроса (секция "ГДЕ") и подставляем в исходный запрос.

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

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

Рекурсивный обход элементов отбора осуществляется следующим алгоритмом (см. следующий скриншот).

Обход элементов отбора и рекурсивное формирование условия запроса

Здесь самым интересным является процедура "ОбработатьЭлементыОтбора", которая дописывает в строковую переменную "ТекстУсловийЗапроса" условия в соответствии с текущем элементом отбора. Рассмотрим код данной процедуры.

Процедура обработки элемента отбора

Процедура, в зависимости от типа переданного элемента отбора (группа или элемент отбора) формирует соответствующий текст условия. Все условия в группе обрамляются скобками, входящие группу также обрамляются круглыми скобками. Условия между выражениями зависят от вышестоящей группы (между верхними в иерархии элементами ставится условие "И").

Если у элемента установлен флаг использования (свойство "Использование") тогда элемент обрабатывается. Формируемый текст зависит также от условия сравнения (Равно, не равно, в списке и прочее). Зависимость формируемого текста условия от вида сравнения можно увидеть в следующей функции.

Функция формирует текст условия для элемента отбора в зависимости от вида сравнения

Еще одной интересной, на мой взгляд, функцией является "ПолучитьТекстПоляПоПредставлению". Нужна она для того, чтобы подставлять в условия запроса поля, которые формируются выражениями языка запроса. Выше в исходный запрос мной было добавлено поле "УровеньРейтинга". Если пользователь будет использовать его в отборе, то в условие запроса нужно подставлять полностью все выражение. Данная функция получает текст поля из запроса по его представлению. Для таких сложных полей она вернет полностью весь текст выражения.

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

Настройки отбора и формируемые для них условия в запросе

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

3. Первый запрос помещаем во временную таблицу и выполняем группировку по итоговым полям с необходимыми агрегатными функциями.

Напомню, что нам нужно получить среднее значение по полю "Рейтинг" и общую сумму по полю "Сумма". Запрос с учетом отборов мы уже сформировали, осталось произвести подсчет итоговых значений. Делается это следующим запросом:

Полный запрос для получения итогов для динамического списка

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

Оптимальность решения

Вообще, данный подход может отрицательно повлиять на производительность. Например, если программа будет подсчитывать итог без установленного отбора в динамическом списке, то количество выбираемых записей может быть огромным (например, за несколько лет). В итоге каждое обновление итогов может продолжаться десятки (!!!) секунд.  Поэтому имеет смысл подсчитывать итоги только если был установлен отбор. Данное условия поставил в тестовой конфигурации.

Плюс ко всему, подсчет итогов, запрос мы можем сделать только на стороне сервера. Поэтому нужно серьезно подойти к обращению на сервер, так как процедура обновления итогов может выполняться очень часто. Представьте себе журнал чеков ККМ в торговой организации, где за минуту может быть введено до 5 чеков, а в час до 300 чеков. Каждый раз после записи документа будет вызываться обновление итогов. Поэтому разумно было бы сократить передаваемый трафик за счет использования внеконтекстных процедур. 

На следующем скриншоте представлен программный код вызова внеконтекстной серверной функции, возвращающей итоги.

Вызов внеконтекстной серверной процедуры получения итогов

Первым параметром передаем отбор динамического списка, вторым структуру типа "ИмяПоляОтбора <-> ТипЗначенияПоляОтбора". Обратите внимание, что первый параметр в функции получается как самостоятельное значение. Не могу сказать точно по какой причине, но если передавать отбор как ссылку, то платформа выдает ошибку о том, что нельзя изменить отбор. Ошибку удалось обойти только подобным образом.

Примечание: использование внеконтекстных процедур позволяет сократить размер передаваемого трафика в разы, так как данные формы на сервер не передаются, в отличии от контекстных серверных процедур (директива "&НаСервере").

Вывод

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

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

Тем не менее, описанный в статье способ имеет место при решении задач.


comments powered by Disqus