При разработке внешних печатных форм (ВПФ) в конфигурациях «1С:Предприятие 8.3», использующих Библиотеку стандартных подсистем (например, 1С:Управление торговлей 11, 1С:ERP или 1С:Комплексная автоматизация 2), разработчики часто сталкиваются с задачей красивого вывода номенклатуры и её характеристик. По умолчанию система может выводить характеристики отдельным столбцом или не выводить детальные свойства вовсе. Часто бизнес-заказчики требуют вывести номенклатуру, а рядом (или в этой же ячейке) отобразить все её индивидуальные свойства (размер, цвет, материал и т. д.) списком через запятую или с новой строки.
В этой статье мы подробно разберем, почему стандартное левое соединение в запросе приводит к ошибкам, как правильно спроектировать пакетный запрос для получения дополнительных реквизитов и как красиво объединить свойства в одну строку на уровне кода печатной формы без потери производительности системы.
Первое побуждение начинающего разработчика — просто добавить ЛЕВОЕ СОЕДИНЕНИЕ таблицы товаров документа с табличной частью дополнительных реквизитов справочника характеристик напрямую в основном запросе печатной формы. Схема выглядит примерно так:
// ВНИМАНИЕ: Этот код содержит архитектурную ошибку задвоения строк!
ВЫБРАТЬ
Товары.Номенклатура КАК Номенклатура,
Товары.Характеристика КАК Характеристика,
ДопРеквизиты.Свойство.Заголовок КАК Свойство,
ДопРеквизиты.Значение КАК Значение
ИЗ
Документ.ЗаказПоставщику.Товары КАК Товары
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ДопРеквизиты
ПО Товары.Характеристика = ДопРеквизиты.Ссылка
Проанализируем ситуацию: если у обуви (характеристика «Синие, Размер 42») заполнено два дополнительных реквизита (Цвет: Синий; Размер: 42), то в результате такого запроса одна строка табличной части документа превратится в две отдельные строки. Платформа честно вернет две записи для одной и той же позиции номенклатуры. При выводе в макет печатной формы это приведет к дублированию строк товаров, искажению итоговых сумм и количества.
Выясним причину: реляционная база данных возвращает плоскую таблицу. Чтобы обойти это ограничение и свернуть свойства в одну ячейку, нам необходимо применить один из трех эффективных способов.
---
Этот способ является наиболее гибким, производительным и безопасным. Мы разделим получение данных на два независимых шага в рамках одного пакетного запроса:
Рассмотрим подробнее реализацию этого алгоритма в кодовой базе.
В функции получения данных для печатной формы (например, ПолучитьДанныеДляПечатнойФормы...) сформируем пакетный запрос. Добавим в него еще один запрос пакета для извлечения свойств:
// Текст запроса для получения свойств характеристик в пакете
ТекстЗапроса = "ВЫБРАТЬ
| Товары.Ссылка КАК Ссылка,
| Товары.НомерСтроки КАК НомерСтроки,
| Товары.Номенклатура КАК Номенклатура,
| Товары.Характеристика КАК Характеристика,
| Товары.КоличествоУпаковок КАК Количество,
| Товары.Сумма КАК Сумма
|ИЗ
| Документ.ЗаказПоставщику.Товары КАК Товары
|ГДЕ
| Товары.Ссылка В(&МассивОбъектов) И Товары.Отменено = ЛОЖЬ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|// ВЫБИРАЕМ СВОЙСТВА ХАРАКТЕРИСТИК ДЛЯ НАШИХ ТОВАРОВ
|ВЫБРАТЬ РАЗЛИЧНЫЕ
| ХарДопРеквизиты.Ссылка КАК Характеристика,
| ХарДопРеквизиты.Свойство.Заголовок КАК Свойство,
| ХарДопРеквизиты.Значение КАК Значение
|ИЗ
| Справочник.ХарактеристикиНоменклатуры.ДополнительныеРеквизиты КАК ХарДопРеквизиты
|ГДЕ
| ХарДопРеквизиты.Ссылка В (
| ВЫБРАТЬ РАЗЛИЧНЫЕ
| Т.Характеристика
| ИЗ
| Документ.ЗаказПоставщику.Товары КАК Т
| ГДЕ
| Т.Ссылка В(&МассивОбъектов)
| )";
Обратите внимание на важную деталь в условии ГДЕ второго запроса пакета. Мы накладываем жесткий отбор по характеристикам, которые реально присутствуют в спецификации печатаемых документов. Это предотвращает выборку лишних данных из базы данных, что критически важно для производительности 1С.
После выполнения пакетного запроса мы выгружаем результаты и помещаем их в структуру, передаваемую в процедуру заполнения табличного документа:
ПакетРезультатовЗапроса = Запрос.ВыполнитьПакет();
СтруктураДанныхДляПечати = Новый Структура;
СтруктураДанныхДляПечати.Вставить("РезультатПоШапке", ПакетРезультатовЗапроса[0]);
// Допустим, под индексом 2 у нас табличная часть товаров
СтруктураДанныхДляПечати.Вставить("РезультатПоТабличнойЧасти", ПакетРезультатовЗапроса[2]);
// Добавляем таблицу доп. характеристик под индексом 3
СтруктураДанныхДляПечати.Вставить("РезультатПоДопХарактеристикам", ПакетРезультатовЗапроса[3]);
Возврат СтруктураДанныхДляПечати;
Теперь перейдем в процедуру заполнения табличного документа (обычно это ЗаполнитьТабличныйДокумент...). Чтобы избежать медленного обращения к базе данных для каждой строки товара, мы один раз выгрузим свойства в Таблицу Значений и проиндексируем её. Это обеспечит мгновенный поиск в памяти сервера.
Посмотрим на пример кода обработки данных:
// Выгружаем товары и дополнительные реквизиты характеристик в таблицы значений
Товары = ДанныеДляПечати.РезультатПоТабличнойЧасти.Выгрузить();
ТаблицаДопХарактеристик = ДанныеДляПечати.РезультатПоДопХарактеристикам.Выгрузить();
// ИНДЕКСИРУЕМ таблицу характеристик для мгновенного поиска по ссылке характеристики
ТаблицаДопХарактеристик.Индексы.Добавить("Характеристика");
Для Каждого СтрокаТовары Из ТаблицаТовары Цикл
// Получаем область строки макета
ОбластьТоварСтроки = Макет.ПолучитьОбласть("СтрокаТаблицы|Товар");
// Сборка дополнительных реквизитов характеристики для текущей строки
ТекстХарактеристики = "";
Если ЗначениеЗаполнено(СтрокаТовары.Характеристика) Тогда
// Быстрый отбор строк свойств по текущей характеристике
ПараметрыОтбора = Новый Структура("Характеристика", СтрокаТовары.Характеристика);
НайденныеСвойства = ТаблицаДопХарактеристик.НайтиСтроки(ПараметрыОтбора);
МассивСвойств = Новый Массив;
Для Каждого СтрокаСвойства Из НайденныеСвойства Цикл
СтрокаСвойстваЗначение = Строка(СтрокаСвойства.Значение);
Если ЗначениеЗаполнено(СтрокаСвойстваЗначение) Тогда
МассивСвойств.Добавить(СтрокаСвойства.Свойство + ": " + СтрокаСвойстваЗначение);
КонецЕсли;
КонецЦикла;
// Объединяем свойства через перенос строки (Символы.ПС) или через запятую
ТекстХарактеристики = СтрСоединить(МассивСвойств, Символы.ПС);
КонецЕсли;
// Используем типовую функцию БСП для получения наименования номенклатуры с характеристикой
ДополнительныеПараметрыПолученияНаименованияДляПечати = НоменклатураКлиентСервер.ДополнительныеПараметрыПредставлениеНоменклатурыДляПечати();
ДополнительныеПараметрыПолученияНаименованияДляПечати.ВозвратнаяТара = СтрокаТовары.ЭтоВозвратнаяТара;
ДополнительныеПараметрыПолученияНаименованияДляПечати.Содержание = СтрокаТовары.Содержание;
// Передаем нашу собранную строку ТекстХарактеристики вместо ссылки характеристики!
ПредставлениеНоменклатурыДляПечати = НоменклатураКлиентСервер.ПредставлениеНоменклатурыДляПечати(
СтрокаТовары.НаименованиеПолное,
ТекстХарактеристики,
,
,
ДополнительныеПараметрыПолученияНаименованияДляПечати
);
// Выводим итоговую строку в параметр макета
УстановитьПараметр(ОбластьТоварСтроки, "Товар", ПредставлениеНоменклатурыДляПечати);
ТабличныйДокумент.Присоединить(ОбластьТоварСтроки);
КонецЦикла;
Использование метода СтрСоединить(МассивСвойств, Символы.ПС) склеит все свойства в одну многострочную строку. В результате в одной ячейке «Товар» под названием номенклатуры красиво отобразится список характеристик:
Ботинки мужские демисезонные
Размер: 42
Цвет: Черный
Материал: Натуральная кожа
---
Если вы работаете в конфигурации 1С:ERP, КА 2 или УТ 11, то можете решить задачу еще проще — с помощью стандартного функционала системы, не меняя код внешней печатной формы.
Разберем этот вариант по шагам:
[Размер] + ", " + [Цвет] + ", " + [Материал]
После этого платформа 1С будет автоматически генерировать поле НаименованиеПолное (или просто Наименование) у характеристик в готовом строковом виде. В тексте запроса вашей печатной формы вам достаточно будет написать:
Товары.Характеристика.НаименованиеПолное КАК ХарактеристикаПредставление
И выводить эту переменную напрямую в макет. Это самый чистый способ решения задачи, заложенный архитекторами фирмы «1С».
---
В сообщениях 17 и 18 на форуме пользователь попытался применить динамическое получение данных через выгрузку объектов в цикле:
// ВНИМАНИЕ: Крайне медленный код! Никогда так не пишите!
Пока ВыбДок.Следующий() Цикл
Для Каждого Стр Из ВыбДок.Товары Цикл
Хар = Стр.Характеристика;
Для Каждого ДопРекв Из Хар.ДополнительныеРеквизиты Цикл
// обращение к Свойству и Значению через точку вызывает неявные SQL-запросы в цикле
ТекстХарактеристики = ТекстХарактеристики + ДопРекв.Свойство.Заголовок + ": " + ДопРекв.Значение;
КонецЦикла;
КонецЦикла;
КонецЦикла;
Проанализируем этот код: обращение через конструкцию вида ДопРекв.Свойство.Заголовок внутри вложенного цикла заставляет систему выполнять отдельные SQL-запросы к базе данных на каждую итерацию каждого свойства каждой строки товара. Если в документе содержится 100 строк, а у каждой характеристики по 3 свойства, система выполнит порядка 300–500 мелких запросов к СУБД. Это приведет к «зависанию» формирования печатной формы на 10–15 секунд и создаст колоссальную нагрузку на сервер.
Всегда используйте пакетные запросы (Решение 1), чтобы получить все данные одним компактным запросом, и обрабатывайте их в оперативной памяти с помощью индексированных таблиц значений.
Для вывода нескольких характеристик номенклатуры в одну ячейку печатной формы используйте передачу свойств отдельной таблицей в пакетном запросе с последующей группировкой и склейкой с помощью СтрСоединить(). Для минимизации доработок кода всегда проверяйте возможность использования встроенных шаблонов наименований характеристик в настройках видов номенклатуры конфигурации.