При разработке обработок группового формирования документов часто возникает типовая задача: выбрать определенный набор данных с помощью запроса и по каждой полученной строке создать отдельный проведенный документ, после чего отобразить список созданных ссылок в таблице на форме. Начинающие разработчики нередко задаются вопросом, можно ли обойтись без цикла или как сделать этот процесс максимально эффективным и архитектурно правильным. Разберем по шагам логику реализации данной задачи, архитектурные ограничения платформы и способы оптимизации кода.
В реляционных СУБД существует конструкция прямого создания записей INSERT INTO ... SELECT. Однако в платформе 1С:Предприятие объектная модель устроена принципиально иначе. Документ в 1С — это не просто запись в таблице базы данных, а сложный бизнес-объект, обладающий собственным модулем, событиями (ПередЗаписью, ПриЗаписи, ОбработкаПроведения), механизмами проверки заполнения и бизнес-логикой движений по регистрам.
По этой причине платформа не поддерживает массовое пакетное создание документов без инстанцирования каждого объекта. Чтобы создать документ, всегда необходимо:
Документы.ИмяДокумента.СоздатьДокумент().Записать() в необходимом режиме (запись или проведение).Таким образом, обход выборки или таблицы значений в цикле является единственным штатным и корректным способом создания множества документов по результатам запроса.
Рассмотрим типовой вариант решения задачи, когда по каждой строке запроса формируется документ и заполняется табличная часть формы:
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Создаем новый экземпляр документа
Док = Документы.УниверсальныйДокумент.СоздатьДокумент();
// Заполняем реквизиты, имена которых совпадают с полями запроса
ЗаполнитьЗначенияСвойств(Док, Выборка);
// Записываем документ с проведением
Док.Записать(РежимЗаписиДокумента.Проведение);
// Отображаем результат на форме
ЗаписьТаблицы = Объект.Документы.Добавить();
ЗаполнитьЗначенияСвойств(ЗаписьТаблицы, Док);
КонецЦикла;
Этот код абсолютно рабочий. Однако при работе с большими объемами данных он может выполняться медленно или вызывать проблемы с блокировками. Проанализируем узкие места и оптимизируем алгоритм.
Выясним причину возможного замедления: если метод Записать() вызывается без открытия явной транзакции, платформа 1С открывает и фиксирует отдельную транзакцию на уровне СУБД для каждого документа. Это приводит к постоянным операциям сброса журнала транзакций на диск (fsync), что сильно замедляет групповую обработку.
Чтобы ускорить процесс, объединим запись документов в явную транзакцию с обязательной обработкой исключений:
НачатьТранзакцию();
Попытка
Пока Выборка.Следующий() Цикл
Док = Документы.УниверсальныйДокумент.СоздатьДокумент();
ЗаполнитьЗначенияСвойств(Док, Выборка);
Док.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Важно: если создаются сотни или тысячи документов, объединять все операции в одну огромную транзакцию опасно из-за роста блокировок и риска взаимных блокировок (deadlocks). В таких случаях рекомендуется фиксировать транзакцию порциями — например, каждые 50–100 документов.
Посмотрим на нюансы заполнения реквизитов:
ЗаполнитьЗначенияСвойств только копирует совпадающие свойства, игнорируя процедуру ОбработкаЗаполнения модуля объекта. Если в документе должны автоматически подставляться валюта, автор или организация по умолчанию, предпочтительнее использовать метод Док.Заполнить(ДанныеЗаполнения).СоздатьДокумент() поле Дата содержит пустую дату. Если дата не передается в запросе, ее необходимо обязательно заполнять значением ТекущаяДатаСеанса(), иначе проведение документа может завершиться ошибкой или нарушить хронологию нумерации.Док.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);.Добавление строк в таблицу формы по одной через Объект.Документы.Добавить() внутри серверного цикла записи объектов создает лишнюю нагрузку. Намного эффективнее накапливать ссылки созданных документов во временной таблице или массиве и по окончании цикла выполнить пакетную загрузку с помощью метода Объект.Документы.Загрузить(ТаблицаСозданных).
Соберем все рекомендации в единый надежный алгоритм:
ТаблицаРезультатов = Новый ТаблицаЗначений;
ТаблицаРезультатов.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("ДокументСсылка.УниверсальныйДокумент"));
Выборка = Запрос.Выполнить().Выбрать();
Счетчик = 0;
РазмерПорции = 100;
НачатьТранзакцию();
Попытка
Пока Выборка.Следующий() Цикл
Док = Документы.УниверсальныйДокумент.СоздатьДокумент();
// Устанавливаем текущую дату, если она не задана в запросе
Док.Дата = ТекущаяДатаСеанса();
ЗаполнитьЗначенияСвойств(Док, Выборка);
// Записываем и проводим
Док.Записать(РежимЗаписиДокумента.Проведение, РежимПроведенияДокумента.Неоперативный);
// Запоминаем ссылку для отображения на форме
НоваяСтрока = ТаблицаРезультатов.Добавить();
НоваяСтрока.Ссылка = Док.Ссылка;
Счетчик = Счетчик + 1;
// Фиксация транзакции порциями
Если Счетчик % РазмерПорции = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ЗаписьЖурналаРегистрации("ГрупповоеСозданиеДокументов", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
// Единовременная загрузка данных в таблицу формы
Объект.Документы.Загрузить(ТаблицаРезультатов);
Такой подход обеспечивает высокую скорость записи в базу данных, защищает от разрастания транзакций и корректно обновляет интерфейс пользователя.