При доработке типовых отчетов в старых редакциях конфигураций (например, «Управление торговлей 10.3» или «Управление производственным предприятием 1.3») разработчики часто сталкиваются с необходимостью вывести дополнительные данные из документов-регистраторов. Типичный пример — вывод поля Комментарий в отчете «Ведомость по денежным средствам». Однако при попытке добавить такое поле через настройки группировок Универсального отчета система нередко выбрасывает ошибку: «Поле не найдено ИсточникДанных.Регистратор.Комментарий».
В рамках этой статьи мы подробно разберем, почему возникает эта ошибка, проанализируем поведение виртуальных таблиц регистров накопления и Построителя отчетов, а также рассмотрим три правильных и безопасных способа решения этой задачи без изменения структуры метаданных конфигурации.
Для того чтобы понять природу ошибки, давайте проанализируем, как работает Универсальный отчет под капотом. В основе старых универсальных отчетов лежит объект ПостроительОтчета. Запрос для него пишется к виртуальным таблицам (например, к таблице ОстаткиИОбороты регистра накопления ДенежныеСредства).
Выясним главные причины возникновения описываемой ошибки:
Регистратор физически присутствует в таблице ОстаткиИОбороты только тогда, когда в параметрах этой таблицы явно указана периодичность Регистратор (или Авто). Если в текущих настройках компоновщика или построителя группировка по регистратору не выбрана пользователем, то оптимизатор запросов СУБД исключает это поле из выборки. Попытка обратиться к нему через точку вызывает ошибку на уровне выполнения запроса.Регистратор имеет составной тип, который включает в себя десятки различных документов. При обращении через точку вида Регистратор.Комментарий система 1С вынуждена выполнять левое соединение со всеми таблицами документов, входящих в этот тип. Если построитель запроса формирует текст динамически, он не всегда может корректно разыменовать свойства составного типа без предварительного приведения типов или жесткого указания полей в схеме.Комментарий у большинства документов имеет тип «Строка неограниченной длины». Системы управления базами данных (СУБД) накладывают жесткое ограничение: такие поля нельзя использовать в секции группировки (GROUP BY). Даже использование конструкции ВЫРАЗИТЬ(... КАК СТРОКА(300)) не спасает, если построитель отчета накладывает выражение динамически поверх уже оптимизированного запроса.Внимание! Решение, к которому часто приходят начинающие разработчики (и к которому пришел автор темы в Сообщении 10) — добавление нового измерения ДокументДвижения в регистр накопления — является критической ошибкой проектирования (антипаттерном). Изменение структуры столь нагруженного регистра ради одного текстового поля приводит к лавинообразному увеличению размера базы данных, существенно снижает производительность системы при проведении документов и превращает любое последующее обновление конфигурации в кошмар. Рассмотрим, как решить эту задачу правильно.
Если перед нами стоит задача решить проблему строго в рамках существующего Универсального отчета (на базе Построителя отчета), нам необходимо вмешаться в процесс формирования текста запроса до того, как построитель инициализирует настройки.
Посмотрим на пример кода, который обычно используется для инициализации группировок:
Процедура УстановитьНачальныеНастройки(ДополнительныеПараметры = Неопределено) Экспорт
// Добавление группировки напрямую может вызвать ошибку, если поле не объявлено в запросе построителя
УниверсальныйОтчет.ДобавитьПолеГруппировка("Комментарий", "Регистратор", "Комментарий", НСтр("ru='Комментарий'"),, "ВЫРАЗИТЬ(ИсточникДанных.Регистратор.Комментарий КАК СТРОКА(300))");
УниверсальныйОтчет.УстановитьНачальныеНастройки(Истина);
КонецПроцедуры
Чтобы избежать ошибки «Поле не найдено», мы должны убедиться, что выражение приведения типа жестко зафиксировано в самом тексте запроса, который передается построителю. Для этого найдем в модуле отчета или в его макете исходный текст запроса и добавим выражение приведения типа в блок описания полей построителя (в секцию {ВЫБРАТЬ...}).
Разберем, как должен выглядеть фрагмент запроса в схеме построителя:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
ИсточникДанных.Организация КАК Организация,
ИсточникДанных.Регистратор КАК Регистратор,
// Явно выражаем тип комментария до группировки
ВЫРАЗИТЬ(ИсточникДанных.Регистратор.Комментарий КАК СТРОКА(300)) КАК РегистраторКомментарий
{ВЫБРАТЬ
Организация.*,
Регистратор.*,
РегистраторКомментарий КАК РегистраторКомментарий}
ИЗ
РегистрНакопления.ДенежныеСредства.ОстаткиИОбороты(&ДатаНач, &ДатаКон, Регистратор, , ) КАК ИсточникДанных
После внесения таких изменений в текст запроса, поле РегистраторКомментарий будет восприниматься построителем как обычная строка фиксированной длины (300 символов). Его можно будет безболезненно использовать в группировках, отборах и выводить в отчет.
Если нам не требуется группировать данные по самому комментарию (то есть нам не нужно, чтобы одинаковые комментарии объединялись в одну строку с подсчетом итогов), то использовать группировку для этого поля не имеет смысла. Правильнее и эффективнее вывести его как дополнительное поле рядом с полем Регистратор.
Механизм дополнительных полей выводит информацию только для детальных записей и не накладывает на них обязательную группировку СГРУППИРОВАТЬ ПО на уровне СУБД. Это позволяет полностью обойти проблему с ограничением длины строки без использования функции ВЫРАЗИТЬ.
Проанализируем программную настройку вывода дополнительного поля:
// Вместо добавления поля группировки:
// УниверсальныйОтчет.ДобавитьПолеГруппировка("Комментарий", ...);
// Добавляем поле как дополнительное (оно будет выводиться в колонке рядом с Регистратором)
УниверсальныйОтчет.ДобавитьДополнительноеПоле("Регистратор.Комментарий", НомерРазмещения, Положение);
При таком подходе система получит значение комментария непосредственно при выводе детальной строки отчета, обратившись к свойству уже выбранного объекта Регистратор. Нагрузка на базу данных при этом будет минимальной, а код останется чистым и понятным.
Наиболее надежным, современным и гибким решением является перевод отчета на Систему компоновки данных (СКД). СКД берет на себя всю рутину по оптимизации тяжелых запросов к составным типам данных и динамическому соединению таблиц.
Рассмотрим, как красиво решить задачу получения комментария регистратора с помощью СКД:
Регистратор.Комментарий, и выполнит оптимальное левое соединение только с теми таблицами документов, которые реально присутствуют в выборке.Регистратор в наборе данных СКД необходимо настроить роль «Период» с типом «Дополнительный период». Это гарантирует корректный расчет остатков и оборотов при любой выбранной пользователем детализации.Для этого в СКД на вкладке «Вычисляемые поля» добавим новое поле КомментарийРегистратора. В колонке «Выражение» укажем вызов функции общего модуля:
// Выражение для вычисляемого поля в СКД
ОбщийМодульОтчетов.ПолучитьКомментарийДокумента(Регистратор)
В самом общем модуле (например, создадим или используем существующий неглобальный модуль ОбщийМодульОтчетов с флагами «Сервер», «Вызов сервера») пропишем простую экспортную функцию:
Функция ПолучитьКомментарийДокумента(ДокументСсылка) Экспорт
Если Не ЗначениеЗаполнено(ДокументСсылка) Тогда
Возврат "";
КонецЕсли;
// Используем безопасное чтение реквизита без загрузки всего объекта в память
Возврат ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ДокументСсылка, "Комментарий");
КонецФункции
Этот метод полностью изолирует базу данных от тяжелых операций соединения строк неограниченной длины в SQL-запросах, обеспечивая максимальную скорость формирования отчета и 100% стабильность его работы.
Мы подробно разобрали причины возникновения ошибки при получении подчиненных полей регистратора и рассмотрели три архитектурно верных способа ее решения. Помните, что внесение изменений в структуру метаданных (добавление новых измерений и реквизитов в стандартные регистры накопления) — это крайняя мера, которой всегда стоит избегать. Использование механизмов Построителя отчета, дополнительных полей или переход на СКД позволяют решать подобные задачи изящно, быстро и безболезненно для последующей поддержки конфигурации.