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