При разработке интерфейсов на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с задачей отображения дополнительных данных в динамических списках. Типичный пример: в регистре сведений есть измерение с типом ДокументСсылка (составной тип, включающий множество видов документов), и нам необходимо вывести в список колонку, указывающую, проведен ли этот документ. Однако при попытке обратиться к полю через точку в конструкторе запроса динамического списка система выдает ошибку "Поле не найдено" или просто не позволяет «развернуть» поле в настройках компоновки.
В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем механизмы работы платформы и рассмотрим несколько эффективных способов решения этой задачи — от простых правок запроса до оптимизированного программного вывода данных.
Разберем ситуацию с технической точки зрения. Динамический список базируется на технологии Системы компоновки данных (СКД). Когда мы обращаемся к реквизиту через точку от поля составного типа (например, Измерение.Проведен), система на уровне СУБД должна выполнить соединение (LEFT JOIN) со всеми таблицами, которые входят в этот составной тип.
Если в составной тип включены «Все документы» или даже 10-20 конкретных видов, количество потенциальных соединений становится огромным. Это приводит к колоссальной деградации производительности при выполнении SQL-запроса. Для защиты от подобных ситуаций в СКД встроены механизмы, которые блокируют обращение к реквизитам «широких» составных типов. Именно поэтому стандартный редактор запроса в динамическом списке не позволяет выбрать такие поля, а при ручном написании кода выдает ошибку.
Рассмотрим подробнее способы обхода этого ограничения.
Если количество видов документов в вашем измерении ограничено и заранее известно, мы можем помочь платформе «понять», к какой именно таблице нужно обратиться. Для этого в тексте запроса динамического списка используется конструкция ВЫРАЗИТЬ. Проанализируем пример, когда в измерении могут быть документы типов РеализацияТоваровУслуг и ПоступлениеТоваровУслуг.
Рассмотрим вариант написания запроса:
ВЫБРАТЬ
Регистр.Период,
Регистр.ПервичныйДокумент,
ВЫБОР
КОГДА ВЫРАЗИТЬ(Регистр.ПервичныйДокумент КАК Документ.РеализацияТоваровУслуг).Проведен ЕСТЬ НЕ NULL
ТОГДА ВЫРАЗИТЬ(Регистр.ПервичныйДокумент КАК Документ.РеализацияТоваровУслуг).Проведен
ИНАЧЕ ВЫРАЗИТЬ(Регистр.ПервичныйДокумент КАК Документ.ПоступлениеТоваровУслуг).Проведен
КОНЕЦ КАК ПроведенПД
ИЗ
РегистрНакопления.ОстаткиТоваров КАК Регистр
Преимущества этого метода:
ПроведенПД будут работать стандартные функции списка: сортировка, группировка и отбор.Недостатки: если типов документов десятки, запрос станет громоздким и трудноподдерживаемым.
Если нам нужно вывести признак проведенности для любого документа (составной тип из всех документов базы), то запрос с ВЫРАЗИТЬ не подойдет. В этом случае мы можем воспользоваться событием динамического списка ПриПолученииДанныхНаСервере. Посмотрим на алгоритм реализации.
Сначала в настройках динамического списка (вкладка «Вычисляемые поля») добавим новое поле, например, ПроведенПД с типом Булево. Выражение оставляем пустым. Затем выносим это поле в элементы формы (в таблицу списка).
Теперь проанализируем программную часть. Важно избегать «запросов в цикле» (так называемый «харам» в разработке 1С), так как это создаст избыточную нагрузку на сервер при каждом скроллинге списка. Правильно будет собрать все ссылки, отображаемые на экране, и получить их состояние одним запросом.
Разберем пример правильной реализации:
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
// Создаем массив ссылок для тех строк, которые сейчас отрисовываются на экране
МассивСсылок = Новый Массив;
Для Каждого КлючИЗначение Из Строки Цикл
// ДокументСсылка находится в данных текущей строки
МассивСсылок.Добавить(КлючИЗначение.Значение.Данные.ПервичныйДокумент);
КонецЦикла;
// Выполняем ОДИН запрос ко всей пачке документов
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Документы.Ссылка КАК Ссылка,
| Документы.Проведен КАК Проведен
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Документы // Здесь может быть обращение к метаданным или общей таблице
|ГДЕ
| Документы.Ссылка В(&МассивСсылок)";
// На практике для универсальности лучше использовать получение через метаданные или кэширование
// Ниже приведен упрощенный пример заполнения вычисляемого поля
Для Каждого СтрокаСписок Из Строки Цикл
ТекущийДокумент = СтрокаСписок.Значение.Данные.ПервичныйДокумент;
// Используем обращение к свойству .Проведен (платформа оптимизирует это при наличии ссылки в кэше)
СтрокаСписок.Значение.Данные.ПроведенПД = ТекущийДокумент.Проведен;
КонецЦикла;
КонецПроцедуры
Важный нюанс: обращение ТекущийДокумент.Проведен внутри этого обработчика допустимо, так как платформа уже получила данные ссылок, но для повышения производительности на больших объемах данных рекомендуется использовать Запрос или ПолучитьОбъект() (в крайнем случае) только для необходимых полей.
Если ваш регистр содержит миллионы записей, а пользователи постоянно используют фильтр по полю «Проведен» в этом списке, лучшим архитектурным решением будет денормализация данных. Рассмотрим этот метод подробнее.
Суть заключается в том, чтобы добавить в сам регистр дополнительный реквизит ПД_Проведен (тип Булево). При записи данных в этот регистр мы будем транслировать состояние документа-измерения в этот реквизит.
Посмотрим на шаги реализации:
ПД_Проведен в структуру регистра.Этот способ гарантирует максимальную производительность, так как исключает любые соединения таблиц и программные пересчеты при выводе списка. Это особенно актуально для высоконагруженных систем.
Если составной тип ограничен всего 2–3 видами документов, можно использовать старое доброе ЛЕВОЕ СОЕДИНЕНИЕ в тексте запроса динамического списка. Рассмотрим пример:
ВЫБРАТЬ
Регистр.Период КАК Период,
Регистр.ПервичныйДокумент КАК ПервичныйДокумент,
ЕСТЬNULL(ДокРеализация.Проведен, ЕСТЬNULL(ДокПоступление.Проведен, ЛОЖЬ)) КАК ПроведенПД
ИЗ
РегистрСведений.МойРегистр КАК Регистр
ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК ДокРеализация
ПО Регистр.ПервичныйДокумент = ДокРеализация.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг КАК ДокПоступление
ПО Регистр.ПервичныйДокумент = ДокПоступление.Ссылка
Проанализируем этот подход: он нагляден и позволяет легко добавлять другие реквизиты документов (дату, номер, контрагента). Однако помните о лимите соединений: если видов документов станет много, SQL-сервер может начать подбирать неоптимальные планы запросов, что замедлит работу формы списка.
Мы рассмотрели различные подходы к решению проблемы вывода реквизитов составного типа в динамический список. Подведем итог, какой метод выбрать в зависимости от ситуации:
ВЫРАЗИТЬ или ЛЕВОЕ СОЕДИНЕНИЕ прямо в тексте запроса. Это обеспечит работоспособность всех стандартных отборов.ПриПолученииДанныхНаСервере с оптимизированным получением данных (без запросов в цикле).Понимание механизмов работы ДинамическогоСписка и СКД позволяет не просто "заставить код работать", но и создавать быстрые, отзывчивые интерфейсы, которые не будут тормозить при росте базы данных.