При разработке отчетов в Системе компоновки данных (СКД) программисты часто сталкиваются с необходимостью производить вычисления, которые выходят за рамки одной текущей строки. Типичные примеры таких задач — расчет нарастающего итога (когда к сумме текущего дня прибавляются суммы всех предыдущих дней) или получение значения из предыдущей строки (например, чтобы узнать разницу курсов валют, динамику изменения цен или остатки на начало периода). В этой статье мы подробно и по шагам разберем, как решать такие задачи элегантно и быстро с помощью встроенных механизмов СКД, не перегружая запрос к базе данных.
Представим ситуацию, описанную пользователем на форуме. У нас есть регистр накопления (например, продажи или обороты по счетам), из которого мы получаем ежедневные обороты. При обычном выводе детальных записей по дням мы видим следующую картину:
Наша цель — получить нарастающий итог, где значение каждого последующего дня включает в себя сумму всех предыдущих периодов:
Для решения этой задачи в платформе 1С предусмотрена мощная и гибкая функция ВычислитьВыражение() (в англоязычной версии — EvalExpression). Она позволяет производить вычисления над набором данных в контексте группировок, обращаться к предыдущим и последующим записям, а также задавать произвольные границы расчета.
Прежде чем перейти к практической реализации, детально проанализируем синтаксис этой функции. Это поможет понять, как именно система управляет выборкой строк при расчете.
Полный синтаксис функции выглядит следующим образом:
ВычислитьВыражение(Выражение, Группировка, Область, Начало, Конец, Сортировка, Иерархия, Формат)
Рассмотрим ключевые параметры, которые нам понадобятся:
"Сумма(СуммаОборот)" или просто имя поля, если мы хотим получить конкретное значение."Первая", "Текущая", "Предыдущая", "Следующая". Также можно задавать смещение числом (например, -1 или 1).Выясним, как настроить расчет нарастающего итога в СКД по шагам. Для этого мы будем использовать вкладки вычисляемых полей и ресурсов компоновки данных.
Шаг 1. Создание вычисляемого поля
Перейдем на вкладку «Вычисляемые поля» нашей схемы компоновки данных. Добавим новую строку:
СуммаНарастающимИтогомШаг 2. Настройка ресурса
Перейдем на вкладку «Ресурсы». Добавим наше только что созданное поле СуммаНарастающимИтогом в список ресурсов. В колонке «Выражение» напишем следующую формулу:
ВычислитьВыражение("Сумма(СуммаОборот)", , , "Первая", "Текущая")
Проанализируем работу этого выражения:
"Сумма(СуммаОборот)" указывает системе, что мы складываем значения ресурса СуммаОборот."Первая" заставляет СКД всегда начинать суммирование с самой первой строки текущей группировки (с самого первого дня нашего отчета)."Текущая" ограничивает расчет текущей строкой. То есть для первой строки это будет только первая строка, для второй — сумма первой и второй, для третьей — сумма первой, второй и третьей строк, и так далее.Шаг 3. Настройка структуры отчета
Перейдем на вкладку «Настройки». Добавим группировку по полю ПериодДень (или любому другому вашему полю периода). В выбранные поля добавим детальное поле СуммаОборот и наше вычисляемое поле-ресурс СуммаНарастающимИтогом. При запуске отчета мы увидим корректно рассчитанный нарастающий итог.
Теперь рассмотрим другую популярную задачу: нам не нужно суммировать все предыдущие строки, нам нужно получить значение конкретно из предыдущей строки. Например, мы хотим вывести цену предыдущего дня рядом с ценой текущего дня, чтобы рассчитать разницу.
Выполним следующие настройки по шагам:
ПредыдущееЗначение.ВычислитьВыражение("ИмяВашегоПоля", , , "Предыдущая", "Предыдущая")
ИмяВашегоПоля — это реквизит или ресурс, значение которого нам нужно получить).*
Важный нюанс настройки: Обратите внимание, что мы указали значение "Предыдущая" как для начала диапазона расчета, так и для его конца. Благодаря этому система берет ровно одну строку, стоящую перед текущей. Для самой первой строки отчета, у которой нет предыдущей, функция вернет значение NULL (пустое значение), что является абсолютно корректным поведением и не приведет к дублированию текущего значения в первой строке.
Ситуация усложняется, когда отчет строится не сверху вниз (вертикальный список дней), а слева направо (кросс-таблица, где периоды выводятся в колонках). В этом случае обычная формула нарастающего итога может отрабатывать некорректно, поскольку по умолчанию вычисления производятся по строкам группировки, а не по колонкам.
Чтобы решить эту задачу, нам необходимо во втором параметре функции явно указать имя группировки колонок, по которой должен идти расчет. Выясним, как это сделать:
Период, и в структуре она названа так же — Период).ВычислитьВыражение("Сумма(СуммаОборот)", "Период", , "Первая", "Текущая")
Благодаря явному указанию группировки "Период" во втором параметре, СКД поймет, что суммирование нужно вести последовательно по колонкам слева направо, а не по строкам сверху вниз.
Иногда разработчики пытаются решить задачу нарастающего итога непосредственно в тексте запроса, избегая использования механизмов СКД. Разберем этот подход, проанализируем его достоинства и недостатки.
Для реализации нарастающего итога в запросе применяется классический прием соединения таблицы самой с собой по условию «дата текущей строки больше или равна дате предыдущих строк».
Посмотрим на пример такого запроса:
ВЫБРАТЬ
Таблица1.Период КАК Период,
Таблица1.СуммаОборот КАК СуммаЗаДень,
СУММА(Таблица2.СуммаОборот) КАК СуммаНарастающимИтогом
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК Таблица1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, ) КАК Таблица2
ПО Таблица1.Период >= Таблица2.Period
СГРУППИРОВАТЬ ПО
Таблица1.Период,
Таблица1.СуммаОборот
УПОРЯДОЧИТЬ ПО
Период
Почему этот метод не рекомендуется использовать в СКД?
Проанализируем минусы реализации нарастающего итога через запрос:
>=) создает квадратичную нагрузку на СУБД. При обработке большого объема данных (например, вывод за несколько лет по дням) такой запрос будет выполняться крайне медленно и может привести к зависанию сервера или превышению времени ожидания транзакции.ВычислитьВыражение() адаптируется под любые пользовательские настройки группировок динамически.Таким образом, использование встроенной функции СКД ВычислитьВыражение() является стандартом разработки в 1С. Этот метод выполняется на уровне процессора компоновки данных в оперативной памяти после получения плоской таблицы результатов из базы данных, что работает в десятки раз быстрее сложных SQL-соединений.
Мы рассмотрели все основные аспекты работы с нарастающими итогами и смежными строками в СКД. Подведем итоги:
ВычислитьВыражение("Сумма(Поле)", , , "Первая", "Текущая")."Предыдущая", "Предыдущая".ПО Таблица1.Дата >= Таблица2.Дата — это убережет вашу систему от лишней нагрузки и обеспечит высокую скорость формирования отчетов даже на больших базах данных.