При разработке интерфейсов в 1С часто возникает задача отображения данных «один ко многим». Используя приемы построения эффективных интерфейсов, можно решить множество проблем юзабилити. Типичный пример — справочник Номенклатура, где у одной позиции может быть несколько штрихкодов. Если мы просто добавим левое соединение с регистром сведений Штрихкоды в запрос динамического списка, мы получим дублирование строк номенклатуры: на каждый штрихкод будет выведена отдельная строка. Рассмотрим, как решить эту задачу правильно, сохранив уникальность строк и обеспечив удобство пользователя.
Проанализируем ситуацию: в языке запросов 1С отсутствует встроенная функция агрегации строк (аналог string_agg в PostgreSQL или GROUP_CONCAT в MySQL). Мы не можем «склеить» строки прямо в Выбрать. Группировка по Максимуму, которую часто используют новички, вернет только один (самый большой) штрихкод, игнорируя остальные.
Выясним причину, почему это сложно реализовать стандартным запросом. Динамический список работает порционно, и для каждой строки ему нужен уникальный ключ (обычно это Ссылка). Если мы выведем штрихкоды через запятую, нам придется либо делать это «на лету» при выводе, либо хранить подготовленную строку в базе данных.
Разберем три основных способа реализации этой задачи.
Этот метод считается классическим для визуального дополнения данных в управляемых формах. Он позволяет «дотянуть» данные из базы только для тех строк, которые в данный момент отображаются на экране пользователя. Это значительно экономит ресурсы сервера и сети.
Для начала добавим в динамический список произвольную колонку Штрихкод (тип — Строка). К слову, по такой же методике часто реализуется флажок в динамическом списке или индикатор прогресса в ячейке, когда нужно вывести нетипичные данные. Затем создадим на форме обработчик события ПриПолученииДанныхНаСервере. Посмотрим на пример программного кода, который реализует сбор штрихкодов:
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
// Подготовим массив ссылок номенклатуры, которые сейчас отображены в списке
МассивНоменклатуры = Строки.ПолучитьКлючи();
// Выполним один запрос ко всем штрихкодам для этого массива ссылок
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Штрихкоды.Владелец КАК Владелец,
| Штрихкоды.Штрихкод КАК Штрихкод
|ИЗ
| РегистрСведений.Штрихкоды КАК Штрихкоды
|ГДЕ
| Штрихкоды.Владелец В(&МассивНоменклатуры)";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
// Используем Соответствие для временного хранения склеенных строк
ТаблицаШтрихкодов = Новый Соответствие;
Пока Выборка.Следующий() Цикл
ТекущийШтрихкод = ТаблицаШтрихкодов.Получить(Выборка.Владелец);
Если ТекущийШтрихкод = Неопределено Тогда
ТаблицаШтрихкодов.Вставить(Выборка.Владелец, Выборка.Штрихкод);
Иначе
ТаблицаШтрихкодов.Вставить(Выборка.Владелец, ТекущийШтрихкод + ", " + Выборка.Штрихкод);
КонецЕсли;
КонецЦикла;
// Заполняем данные оформления в списке
Для Каждого КлючИЗначение Из Строки Цикл
ТекстШтрихкодов = ТаблицаШтрихкодов.Получить(КлючИЗначение.Ключ);
Если ТекстШтрихкодов <> Неопределено Тогда
КлючИЗначение.Значение.Данные.Штрихкод = ТекстШтрихкодов;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Важный нюанс: Обратите внимание, что мы делаем запрос один раз для всей пачки строк (параметр Строки), а не в цикле по каждой строке. Это критически важно для производительности. Если делать запрос внутри цикла Для Каждого, динамический список будет «тормозить» при прокрутке.
Проанализируем ситуацию, возникшую у автора темы: «когда вбиваешь штрихкод в поиск, ничего не находит». Это происходит потому, что ПриПолученииДанныхНаСервере меняет только отображение данных. В самой базе данных (в таблице динамического списка) этого поля нет, поэтому стандартный поиск 1С по нему не работает. Это особенно критично, если вы используете мобильный терминал сбора данных и вам нужно быстро находить товар по любому из его кодов.
Чтобы поиск работал корректно, рекомендуем использовать метод денормализации данных:
Номенклатура новый реквизит ВсеШтрихкоды (длинная строка, неограниченная длина не рекомендуется, лучше 100-200 символов с индексацией).ПриЗаписи для регистра сведений Штрихкоды (или внесем изменения в модуль набора записей).ВсеШтрихкоды соответствующего объекта Номенклатура.Рассмотрим пример функции для обновления этого реквизита:
Процедура ОбновитьСтрокуШтрихкодов(СсылкаНаНоменклатуру) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Штрихкоды.Штрихкод
|ИЗ
| РегистрСведений.Штрихкоды КАК Штрихкоды
|ГДЕ
| Штрихкоды.Владелец = &Владелец";
Запрос.УстановитьПараметр("Владелец", СсылкаНаНоменклатуру);
Выборка = Запрос.Выполнить().Выбрать();
МассивШтрихкодов = Новый Массив;
Пока Выборка.Следующий() Цикл
МассивШтрихкодов.Добавить(Строка(Выборка.Штрихкод));
КонецЦикла;
ОбъектНоменклатура = СсылкаНаНоменклатуру.ПолучитьОбъект();
ОбъектНоменклатура.ВсеШтрихкоды = СтрСоединить(МассивШтрихкодов, ", ");
ОбъектНоменклатура.Записать();
КонецПроцедуры
Преимущество этого метода в том, что в динамическом списке нам вообще не нужно писать код. Мы просто выводим поле ВсеШтрихкоды, и по нему мгновенно работает поиск, сортировка и отбор.
Если ваш динамический список построен на произвольном запросе, можно задействовать механизмы системы компоновки данных (СКД). Этот прием отдаленно напоминает задачу, когда требуется вывести несколько картинок в одну ячейку на СКД, так как в обоих случаях мы программно готовим составное значение для одной ячейки. Выясним, как это сделать:
СписокШтрихкодов.ОбщегоНазначения.СформироватьСписокШтрихкодов(Ссылка).Сервер.Посмотрим на пример такой функции:
Функция СформироватьСписокШтрихкодов(НоменклатураСсылка) Экспорт
// Логика получения штрихкодов через СтрСоединить()
// Внимание: метод может замедлять список, если данных много
КонецФункции
Важно: Этот способ удобен в реализации, но он также не решает проблему поиска и может создавать нагрузку на сервер, так как функция будет вызываться для каждой строки списка без оптимизации пакетов.
Рассмотрим ситуацию, когда у товара может быть очень много штрихкодов (например, 100 и более). В некоторых случаях может потребоваться даже чтение штрих-кодов из PDF или картинок, если они хранятся в виде присоединенных файлов. Но выводить их все через запятую в одной ячейке — плохая практика.
Проанализируем профессиональные интерфейсные решения:
ПриПолученииДанныхНаСервере можно заполнять не только само значение ячейки, но и ее ТекстПодсказки. В ячейке пользователь видит основной код, а при наведении мыши — полный список через запятую.Подводя итог, можно сказать: если вам нужно только отображение — используйте ПриПолученииДанныхНаСервере с пакетным запросом. Если же критически важен поиск по этим штрихкодам прямо в списке — выбирайте путь с созданием дополнительного реквизита в справочнике Номенклатура.