Как заполнить табличную часть документа данными из табличной части справочника в 1С

Программист 1С v8.3 (Обычные формы) Сфера услуг, туризм и социальный сектор
← На главную

При разработке учетных решений нередко возникает задача: перенести состав спецификации, рецепта или калькуляции из справочника в строки документа. Например, в документе Счет пользователь указывает элемент справочника Рецепты, и система должна автоматически развернуть и заполнить подчиненную номенклатуру, количество и суммы.

Разберем типичные ошибки при реализации такого алгоритма, рассмотрим способы заполнения через обращение к свойствам объекта и реализуем оптимальный пакетный алгоритм с использованием языка запросов 1С.

Анализ типовой ошибки: выполнение запроса внутри цикла

Рассмотрим вариант, к которому часто приходят начинающие разработчики: при обходе строк документа для каждой строки отправляется отдельный запрос к базе данных.


Процедура ЗаполнитьПоРецепту()
    Для Каждого СтрокаМедикамент Из Медикаменты Цикл
        Если СтрокаМедикамент.Рецепт.Пустая() Тогда
            Продолжить;
        КонецЕсли;
        
        З = Новый Запрос;
        З.Текст = 
            "ВЫБРАТЬ
            |   РецептМедикаменты.Номенклатура,
            |   РецептМедикаменты.Количество,
            |   РецептМедикаменты.Сумма
            |ИЗ
            |   Справочник.Рецепт.Медикаменты КАК РецептМедикаменты
            |ГДЕ
            |   РецептМедикаменты.Ссылка = &Рецепт";
        З.УстановитьПараметр("Рецепт", СтрокаМедикамент.Рецепт);
        Рез = З.Выполнить().Выгрузить();
        
        Если Рез.Количество() = 1 Тогда
            ЗаполнитьЗначенияСвойств(СтрокаМедикамент, Рез[0]);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Проанализируем недостатки этого фрагмента:

  1. Запрос в цикле. Вызов Запрос.Выполнить() на каждой итерации цикла генерирует отдельное обращение к СУБД. При обработке больших табличных частей это приводит к критическому падению производительности и блокировкам данных.
  2. Ограничение на количество строк. Условие Рез.Количество() = 1 приводит к тому, что если рецепт содержит более одной позиции медикаментов, данные вообще не будут перенесены в строку документа.

Вариант 1. Заполнение при интерактивном выборе рецепта в строке

Если требуется заполнять состав сразу в момент выбора рецепта в форме документа, назначим обработчик события ПриИзменении для колонки Рецепт табличной части.

Когда один рецепт может содержать несколько строк номенклатуры, первую позицию рецепта помещаем в текущую редактируемую строку, а для последующих строк вызываем добавление новых строк в табличную часть документа.


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

Вариант 2. Быстрое заполнение первой позиции через обращение к свойствам

Если по бизнес-логике конфигурации в рецепте всегда гарантированно хранится ровно одна позиция медикамента, можно обойтись прямым обращением к табличной части через точку без выполнения тяжелых конструкций:


Если СтрокаМедикамент.Рецепт.Медикаменты.Количество() > 0 Тогда
    ЗаполнитьЗначенияСвойств(СтрокаМедикамент, СтрокаМедикамент.Рецепт.Медикаменты[0]);
КонецЕсли;

Важно: прямое обращение через точку к табличной части ссылочного типа (Рецепт.Медикаменты) считывает объект базы данных целиком в оперативную память. Для групповых обработок или больших объемов данных всегда следует отдавать предпочтение запросам.

Вариант 3. Оптимальное пакетное заполнение кнопки «Заполнить по рецептам»

Разберем по шагам правильную реализацию групповой обработки табличной части всей формы документа, которая исключает запросы в цикле и корректно разворачивает многострочные рецепты.

  1. Собираем массив всех уникальных ссылок на рецепты, указанных в табличной части документа.
  2. Выполняем один общий запрос к базе данных с условием Ссылка В (&МассивРецептов).
  3. Очищаем или перезаполняем табличную часть новыми строками с сопоставлением полей.

Процедура ЗаполнитьТабличнуюЧастьПоРецептам()
    // 1. Получаем массив рецептов из документа
    МассивСсылок = Медикаменты.ВыгрузитьКолонку("Рецепт");
    
    // Исключаем пустые значения
    Индекс = МассивСсылок.Количество() - 1;
    Пока Индекс >= 0 Цикл
        Если Не ЗначениеЗаполнено(МассивСсылок[Индекс]) Тогда
            МассивСсылок.Удалить(Индекс);
        КонецЕсли;
        Индекс = Индекс - 1;
    КонецЦикла;
    
    Если МассивСсылок.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // 2. Выполняем один пакетный запрос ко всем необходимым рецептам
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ РАЗЛИЧНЫЕ
        |   ТЧ.Ссылка КАК Рецепт,
        |   ТЧ.Номенклатура КАК Номенклатура,
        |   ТЧ.Количество КАК Количество,
        |   ТЧ.Сумма КАК Сумма
        |ИЗ
        |   Справочник.Рецепт.Медикаменты КАК ТЧ
        |ГДЕ
        |   ТЧ.Ссылка В (&МассивСсылок)
        |УПОРЯДОЧИТЬ ПО
        |   ТЧ.Ссылка,
        |   ТЧ.НомерСтроки";
    Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
    
    ТаблицаСостава = Запрос.Выполнить().Выгрузить();
    
    // 3. Перезаполняем табличную часть документа
    Медикаменты.Очистить();
    Для Каждого Стр Из ТаблицаСостава Цикл
        НоваяСтрока = Медикаменты.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, Стр);
    КонецЦикла;
КонецПроцедуры

Использование метода ЗаполнитьЗначенияСвойств() в паре с одним запросом позволяет переносить любые совпадающие по именам реквизиты (Номенклатура, Количество, Сумма) быстро, надежно и без избыточной нагрузки на сервер 1С и СУБД.

← На главную