При разработке сложных отчетов в системе компоновки данных (СКД) часто возникает необходимость сравнивать значения текущей строки с предыдущей или следующей. Классический пример — вычисление временного интервала между созданием документов, чтобы выявить слишком быструю (возможно, автоматическую или ошибочную) генерацию записей. Однако, если расчет в детальных записях реализуется относительно просто, то получение корректных итогов по группировкам (например, по ответственному менеджеру или подразделению) часто ставит разработчиков в тупик.
В этой статье мы подробно разберем, как заставить СКД правильно суммировать результаты вычислений, зависящих от порядка строк, и какие функции компоновки данных нам в этом помогут.
Рассмотрим ситуацию: нам нужно пометить документы, дата создания которых отличается от предыдущего документа менее чем на 5 минут. Для детальных записей (самый нижний уровень отчета) 1С предоставляет мощный инструмент — функцию ВычислитьВыражение. Она позволяет обращаться к контексту других строк набора данных.
Для реализации логики в детальных записях мы можем создать вычисляемое поле (назовем его ШтрафПоСозданию) и прописать в нем следующее выражение:
Выбор
Когда РазностьДат(ВычислитьВыражение("ДатаСоздания", , , "Предыдущая", "Предыдущая"), ДатаСоздания, "Минута") < 5
Тогда 100
Иначе 0
Конец
Проанализируем, как это работает: функция ВычислитьВыражение с параметрами "Предыдущая", "Предыдущая" возвращает значение поля ДатаСоздания из строки, стоящей непосредственно перед текущей. Функция РазностьДат вычисляет разницу в минутах. Если условие выполняется, мы назначаем "штраф" в 100 единиц.
Это выражение отлично работает на уровне детальных записей, но как только мы добавляем группировку (например, по Ответственному), стандартный ресурс Сумма(ШтрафПоСозданию) может выдать 0 или неверное значение. Это происходит потому, что в контексте группировки СКД не понимает, как "складывать" выражения, которые сами по себе зависят от порядка строк.
Когда СКД рассчитывает итог по группировке, она пытается вычислить выражение ресурса. Если в ресурсе просто указано поле, содержащее ВычислитьВыражение, компоновщик может потерять связь с детальными записями. Ему нужно явно указать, что мы хотим сначала вычислить значения для каждой строки внутри группы, а затем их просуммировать.
Разберем типичную ошибку "Выражение не может быть вычислено". Она часто возникает, если поле, используемое внутри ВычислитьВыражение (например, ДатаСоздания), не включено в группировку или к нему не применена агрегатная функция. Для ресурсов все поля должны быть однозначно определены в контексте текущей строки итога.
Для корректного расчета итогов в СКД существует специальная функция — ВычислитьВыражениеТаблица. Она позволяет получить таблицу значений, сформированную на основе детальных записей, и выполнить над ней агрегатную операцию (Сумма, Количество и т.д.).
Чтобы рассчитать сумму штрафов по группировке, в колонке "Выражение" на закладке "Ресурсы" следует прописать конструкцию следующего вида:
Сумма(ВычислитьВыражениеТаблица("Выбор Когда РазностьДат(ВычислитьВыражение(""Дата"", , , ""Предыдущая"", ""Предыдущая""), Дата, ""Минута"") < 5 Тогда 100 Иначе 0 Конец", "Дата"))
Разберем по шагам, что здесь происходит:
ВычислитьВыражениеТаблица обходит все записи текущей группировки."Дата") — это поле, по которому будет строиться временная таблица (сортировка внутри функции).Сумма складывает все полученные результаты из этой временной таблицы.Важно помнить, что расчет "предыдущей" записи должен быть ограничен текущей группировкой. Если мы считаем документы по Ответственному №1, мы не должны сравнивать его первый документ с последним документом Ответственного №2.
Для этого в функции ВычислитьВыражение второй параметр (Группировка) должен иметь значение "Группировка". Это указывает СКД, что область поиска "предыдущей" записи ограничена текущей веткой иерархии.
Также критически важна сортировка. Если в настройках отчета не задан жесткий порядок (например, по Дате), то "предыдущая" запись может определяться случайным образом (как документы лежат в базе). Проанализируем пример правильной настройки в ресурсах:
Сумма(ВычислитьВыражениеТаблица("Выбор Когда РазностьДат(ВычислитьВыражение(""Дата"", ""Группировка"", , ""Предыдущая"", ""Предыдущая""), Дата, ""Минута"") < 5 Тогда 100 Иначе 0 Конец", "Дата"))
При расчете для самой первой строки в группе функция ВычислитьВыражение(..., "Предыдущая") вернет 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С оптимизирует такие соединения на уровне СУБД.
Мы рассмотрели два основных способа решения проблемы. Использование ВычислитьВыражениеТаблица в ресурсах — это гибкий и быстрый способ настройки без изменения текста запроса, который отлично подходит для небольших и средних отчетов. Перенос логики в запрос — профессиональный подход для высоконагруженных систем, гарантирующий стабильность итогов в любых группировках. Выбор метода зависит от сложности вашей схемы компоновки и требований к производительности.