В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью вывести в форму списка данные из подчиненных таблиц. Классический пример: в списке заказов нужно показать всех водителей, назначенных на этот заказ через разные путевые листы. Ситуация осложняется, если информационная база работает в режиме совместимости 8.2.13 или ниже, где отсутствуют современные инструменты СКД (например, функция СоединитьСтроки в запросе динамического списка) и серверные события обработки строк.
В данной статье мы разберем, как решить задачу объединения строк ("многие к одному") для динамического списка, проанализируем архитектурные подходы и выберем наиболее стабильный вариант.
Рассмотрим ситуацию: у нас есть документ ЗаявкаНаВывоз. По одной заявке может быть сформировано несколько документов ПутевойЛист, в каждом из которых указан свой Водитель. Нам нужно в списке заявок видеть колонку "Водители", где фамилии перечислены через запятую.
Проблема заключается в том, что обычный левый запрос в динамическом списке размножит строки основной таблицы (одна заявка — пять строк, если пять водителей). Использование временных таблиц или вложенных запросов с агрегацией строк в языке запросов 1С напрямую невозможно. В современных версиях платформы это решается через расширение языка запросов СКД, но в старых режимах совместимости (8.2.13) этот функционал недоступен.
Проанализируем самый надежный способ, который обеспечит высокую скорость работы интерфейса и возможность полноценного поиска. Мы будем хранить уже готовую строку с водителями непосредственно в базе данных. Это называется денормализацией.
Вариант А: Дополнительный реквизит в документе.
В документ ЗаявкаНаВывоз добавляется строковый реквизит СписокВодителей (длина 200–500 символов). При записи любого путевого листа, связанного с этой заявкой, мы пересчитываем строку и записываем её в заявку.
Вариант Б: Независимый регистр сведений.
Если вы не хотите менять структуру самого документа, создадим регистр сведений СведенияОВодителяхЗаявок.
Измерение: Заявка (Ссылка), Ресурс: СписокВодителей (Строка).
Рассмотрим реализацию через Подписки на события, чтобы не менять код существующих документов:
ПриЗаписи для документа ПутевойЛист.
Процедура ОбновитьСписокВодителейВЗаявке(ЗаявкаСсылка) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ПутевойЛист.Водитель.Наименование КАК ФИО
|ИЗ
| Документ.ПутевойЛист КАК ПутевойЛист
|ГДЕ
| ПутевойЛист.ДокументОснование = &Заявка
| И ПутевойЛист.Проведен = ИСТИНА";
Запрос.УстановитьПараметр("Заявка", ЗаявкаСсылка);
Результат = Запрос.Выполнить().Выбрать();
МассивВодителей = Новый Массив;
Пока Результат.Следующий() Цикл
МассивВодителей.Добавить(Результат.ФИО);
КонецЦикла;
СтрокаВодителей = СтрСоединить(МассивВодителей, ", ");
// Записываем в регистр или реквизит документа
Менеджер = РегистрыСведений.СведенияОВодителяхЗаявок.СоздатьМенеджерЗаписи();
Менеджер.Заявка = ЗаявкаСсылка;
Менеджер.СписокВодителей = СтрокаВодителей;
Менеджер.Записать();
КонецПроцедуры
Если режим совместимости позволяет использовать событие ПриПолученииДанныхНаСервере (доступно с версии 8.3.8, но мы рассмотрим его как альтернативу для тех, кто может поднять режим), то можно обойтись без хранения данных в БД. Выясним, как это работает: динамический список запрашивает только те строки, которые видны на экране (например, 20-30 штук). Мы перехватываем эти ссылки и одним запросом получаем по ним водителей.
Разберем алгоритм в модуле формы:
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
// Получаем массив ссылок заявок, которые сейчас отображаются
КлючиЗаявок = Строки.ПолучитьКлючи();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПутевойЛист.ДокументОснование КАК Заявка,
| ПутевойЛист.Водитель.Наименование КАК Водитель
|ИЗ
| Документ.ПутевойЛист КАК ПутевойЛист
|ГДЕ
| ПутевойЛист.ДокументОснование В (&КлючиЗаявок)
|ИТОГИ ПО
| Заявка";
Запрос.УстановитьПараметр("КлючиЗаявок", КлючиЗаявок);
ВыборкаЗаявка = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапоса.ПоГруппировкам);
Пока ВыборкаЗаявка.Следующий() Цикл
МассивФИО = Новый Массив;
Выборка = ВыборкаЗаявка.Выбрать();
Пока Выборка.Следующий() Цикл
МассивФИО.Добавить(Выборка.Водитель);
КонецЦикла;
// Находим строку в оформлении списка и подставляем текст
ДанныеСтроки = Строки[ВыборкаЗаявка.Заявка];
ДанныеСтроки.Данные["ВодителиКолонка"] = СтрСоединить(МассивФИО, ", ");
КонецЦикла;
КонецПроцедуры
Важно: У этого метода есть существенный минус — по колонке "Водители" будет невозможно сделать стандартную сортировку или поиск, так как данных нет в базе данных, они вычисляются "на лету" только для отображения.
Если мы выбрали Решение 1 (хранение строки), то поиск работает "из коробки". Но что делать, если нужно отфильтровать список по конкретному водителю (выбор из справочника), а не просто искать по подстроке?
Проанализируем ситуацию: пользователь выбирает водителя в шапке формы. Мы должны наложить отбор на динамический список. Выясним, как это сделать эффективно:
ОтборВодитель (тип СправочникСсылка.Водители).ПриИзменении этого реквизита будем программно устанавливать отбор по полю Ссылка документа ЗаявкаНаВывоз.
&НаСервере
Процедура УстановитьОтборПоВодителю()
// Сначала получим все заявки, где участвует этот водитель
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ ДокументОснование ИЗ Документ.ПутевойЛист ГДЕ Водитель = &Водитель";
Запрос.УстановитьПараметр("Водитель", ОтборВодитель);
СписокЗаявок = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("ДокументОснование");
// Устанавливаем отбор в динамическом списке
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
Список, "Ссылка", СписокЗаявок, ВидСравненияКомпоновкиДанных.ВСписке, , ЗначениеЗаполнено(ОтборВодитель)
);
КонецПроцедуры
Для старых режимов совместимости (8.2.13) оптимальным решением является комбинированный подход:
Рассмотренные методы позволяют обойти жесткие ограничения платформы, сохраняя при этом удобство пользователя и производительность системы.