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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разберем классическую задачу, с которой часто сталкиваются начинающие (да и опытные) разработчики в 1С: автоматическое заполнение одной табличной части (ТЧ) на основании данных другой ТЧ с использованием регистра сведений. Проанализируем ситуацию на конкретном примере.

Постановка задачи: Имеется документ Сборка. В нем есть две табличные части: первая — ПК (содержит перечень компьютеров и их количество), вторая — Комплектующие (содержит детали для сборки). Также существует регистр сведений ИнфоПК, где хранится спецификация: для какого компьютера (измерение ПК) какая деталь (измерение Комплектующая) и в каком объеме (ресурс Количество) требуется. Необходимо по нажатию кнопки заполнить вторую ТЧ списком всех деталей, необходимых для сборки компьютеров, указанных в первой ТЧ.

Частые ошибки при решении задачи

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


&НаКлиенте
Процедура Заполнить(Команда)
    Объект.Комплектующие.Очистить();
    ТЧПК = Объект.ПК;
    Для Каждого СтрокаТЗ Из ТЧПК Цикл
        ПК = СтрокаТЗ.Наименование;
        Количество = СтрокаТЗ.Количество;
        ПКНаименованиеПриИзмененииНаСервере(ПК, Количество);
    КонецЦикла;
КонецПроцедуры

В этом коде кроется серьезная архитектурная проблема, которую в 1С называют антипаттерном. Вызов серверной процедуры ПКНаименованиеПриИзмененииНаСервере внутри цикла на клиенте приводит к многократным вызовам сервера. При каждом таком вызове происходит передача контекста формы с клиента на сервер и обратно. Если в табличной части ПК будет 100 строк, система совершит 100 серверных вызовов, что катастрофически снизит производительность.

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

Как передать список значений в запрос?

Выясним причину, по которой разработчики прибегают к циклу на клиенте: это неумение передать сразу все данные из табличной части в запрос. Чтобы сделать ровно один вызов сервера, нам нужно собрать все необходимые компьютеры и передать их разом. Для этого используется Массив.

Массив — это простой список значений. Проанализируем, как заполнить массив на сервере и передать его в запрос:


МассивПК = Новый Массив;
Для Каждого ТекСтрока из Объект.ПК Цикл
    МассивПК.Добавить(ТекСтрока.Наименование);
КонецЦикла;

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ИнфоПК.Комплектующая КАК Комплектующая,
    |    СУММА(ИнфоПК.Количество) КАК Количество
    |ИЗ
    |    РегистрСведений.ИнфоПК КАК ИнфоПК
    |ГДЕ
    |    ИнфоПК.ПК В (&МассивПК)
    |
    |СГРУППИРОВАТЬ ПО
    |    ИнфоПК.Комплектующая";

Запрос.УстановитьПараметр("МассивПК", МассивПК);

Используя конструкцию В (&МассивПК) в условии ГДЕ, мы заставляем запрос выбрать спецификации для всех ПК сразу.

Как избавиться от задвоения строк (дублей)?

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

Способ 1: Программная свертка табличной части

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


ТЗ = Объект.Комплектующие.Выгрузить();
ТЗ.Свернуть("Комплектующая", "Количество");
Объект.Комплектующие.Загрузить(ТЗ);

Этот метод работает, но он не самый оптимальный, так как требует дополнительных действий процессора по обработке уже полученных данных вне запроса.

Способ 2: Оптимальное решение через Временные Таблицы

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

  1. На клиенте мы вызываем процедуру сервера ровно один раз по нажатию кнопки на форме.
  2. На сервере мы выгружаем первую табличную часть в таблицу значений и передаем её как параметр в запрос.
  3. В запросе помещаем эту таблицу во Временную Таблицу. При этом мы сразу группируем одинаковые компьютеры, если пользователь ввел их дважды.
  4. Связываем временную таблицу с регистром сведений через ВНУТРЕННЕЕ СОЕДИНЕНИЕ по полю ПК.
  5. Перемножаем количество заказанных компьютеров на норму деталей из регистра и группируем результат по комплектующим.
  6. Загружаем результат выборки сразу во вторую ТЧ.

Посмотрим на пример итогового оптимального кода:


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

&НаКлиенте
Процедура Заполнить(Команда)
    ЗаполнитьКомплектующиеНаСервере();
КонецПроцедуры

Преимущества такого подхода:

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

← На главную