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