Как рассчитать итоги по ресурсам с использованием функции ВычислитьВыражение в СКД?

Программист 1С v8.3 (Обычные формы) Управленческий учет IT и автоматизация бизнеса
← На главную

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

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

Анализ исходной задачи и работа с детальными записями

Рассмотрим ситуацию: нам нужно пометить документы, дата создания которых отличается от предыдущего документа менее чем на 5 минут. Для детальных записей (самый нижний уровень отчета) 1С предоставляет мощный инструмент — функцию ВычислитьВыражение. Она позволяет обращаться к контексту других строк набора данных.

Для реализации логики в детальных записях мы можем создать вычисляемое поле (назовем его ШтрафПоСозданию) и прописать в нем следующее выражение:


Выбор 
    Когда РазностьДат(ВычислитьВыражение("ДатаСоздания", , , "Предыдущая", "Предыдущая"), ДатаСоздания, "Минута") < 5 
    Тогда 100 
    Иначе 0 
Конец

Проанализируем, как это работает: функция ВычислитьВыражение с параметрами "Предыдущая", "Предыдущая" возвращает значение поля ДатаСоздания из строки, стоящей непосредственно перед текущей. Функция РазностьДат вычисляет разницу в минутах. Если условие выполняется, мы назначаем "штраф" в 100 единиц.

Это выражение отлично работает на уровне детальных записей, но как только мы добавляем группировку (например, по Ответственному), стандартный ресурс Сумма(ШтрафПоСозданию) может выдать 0 или неверное значение. Это происходит потому, что в контексте группировки СКД не понимает, как "складывать" выражения, которые сами по себе зависят от порядка строк.

Проблема агрегации: почему обычная сумма не работает

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

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

Решение через функцию ВычислитьВыражениеТаблица

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

Чтобы рассчитать сумму штрафов по группировке, в колонке "Выражение" на закладке "Ресурсы" следует прописать конструкцию следующего вида:


Сумма(ВычислитьВыражениеТаблица("Выбор Когда РазностьДат(ВычислитьВыражение(""Дата"", , , ""Предыдущая"", ""Предыдущая""), Дата, ""Минута"") < 5 Тогда 100 Иначе 0 Конец", "Дата"))

Разберем по шагам, что здесь происходит:

  1. Функция ВычислитьВыражениеТаблица обходит все записи текущей группировки.
  2. Первый параметр — это само выражение, которое нужно вычислить для каждой строки. Обратите внимание на удвоенные кавычки внутри строки.
  3. Второй параметр ("Дата") — это поле, по которому будет строиться временная таблица (сортировка внутри функции).
  4. Внешняя функция Сумма складывает все полученные результаты из этой временной таблицы.

Учет контекста группировки и сортировки

Важно помнить, что расчет "предыдущей" записи должен быть ограничен текущей группировкой. Если мы считаем документы по Ответственному №1, мы не должны сравнивать его первый документ с последним документом Ответственного №2.

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

Также критически важна сортировка. Если в настройках отчета не задан жесткий порядок (например, по Дате), то "предыдущая" запись может определяться случайным образом (как документы лежат в базе). Проанализируем пример правильной настройки в ресурсах:


Сумма(ВычислитьВыражениеТаблица("Выбор Когда РазностьДат(ВычислитьВыражение(""Дата"", ""Группировка"", , ""Предыдущая"", ""Предыдущая""), Дата, ""Минута"") < 5 Тогда 100 Иначе 0 Конец", "Дата"))

Обработка пустых значений (NULL)

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

Рассмотрим улучшенный вариант выражения:


РазностьДат(ЕстьNull(ВычислитьВыражение("Дата", , , "Предыдущая", "Предыдущая"), Дата), Дата, "Минута")

В данном случае, если предыдущей даты нет, функция вернет текущую дату, разность составит 0 минут, и условие < 5 сработает. Однако, для первой записи штрафа быть не должно. Поэтому правильнее проверять на NULL отдельно:


Выбор 
    Когда ВычислитьВыражение("Дата", , , "Предыдущая", "Предыдущая") Есть NULL Тогда 0
    Когда РазностьДат(ВычислитьВыражение("Дата", , , "Предыдущая", "Предыдущая"), Дата, "Минута") < 5 Тогда 100 
    Иначе 0 
Конец

Альтернативный метод: расчет на уровне запроса

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

Выясним, как это сделать. Мы можем соединить таблицу саму с собой по условию "предыдущей" записи. Разберем пример кода в запросе:


ВЫБРАТЬ
    Т1.Ссылка КАК Документ,
    Т1.Ответственный,
    Т1.Дата КАК ТекущаяДата,
    МАКСИМУМ(Т2.Дата) КАК ПредыдущаяДата
ПОМЕСТИТЬ ВТ_СДатами
ИЗ
    Документ.ПриходныйКассовыйОрдер КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПриходныйКассовыйОрдер КАК Т2
    ПО Т1.Ответственный = Т2.Ответственный
    И Т2.Дата < Т1.Дата
ГРУППИРОВАТЬ ПО
    Т1.Ссылка, Т1.Ответственный, Т1.Дата
;

ВЫБРАТЬ
    *,
    ВЫБОР КОГДА РАЗНОСТЬДАТ(ПредыдущаяДата, ТекущаяДата, МИНУТА) < 5 ТОГДА 100 ИНАЧЕ 0 КОНЕЦ КАК Штраф
ИЗ
    ВТ_СДатами

В этом случае поле Штраф становится обычным числовым полем набора данных. В СКД останется лишь добавить его в ресурсы с функцией Сумма(Штраф). Этот метод работает быстрее на больших объемах данных, так как платформа 1С оптимизирует такие соединения на уровне СУБД.

Заключение

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

← На главную