Как эффективно заполнить табличную часть документа данными из таблицы значений, полученной запросом?

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

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

Рассмотрим типовую ситуацию, с которой столкнулся автор вопроса. У нас есть запрос, который формирует таблицу значений (далее ТЗ) с необходимыми данными, например, о маршрутах, водителях, автомобилях, пробеге и других показателях. Задача состоит в том, чтобы перенести эти данные в табличную часть некоторого документа. Автор в своем коде попытался сделать это путем перебора строк табличной части документа и присваивания им значений из колонок всей ТЗ, что является фундаментальной ошибкой, так как переменная Т в его примере представляет всю таблицу значений, а не отдельную ее строку. Например, запись Стр.Маршрут = Т.Маршрут; некорректна, поскольку Т.Маршрут — это колонка таблицы значений, а не значение для текущей строки.


// Некорректный код автора
Для Каждого Стр Из Маршруты Цикл
    Стр.Маршрут = Т.Маршрут; // Ошибка: Т.Маршрут - это колонка, а не значение текущей строки ТЗ
    Стр.Машины = Т.Автомобиль;
    // ... и так далее
КонецЦикла;

Чтобы правильно решить эту задачу, мы можем использовать несколько подходов, каждый из которых имеет свои преимущества и области применения. Мы подробно разберем каждый из них.

Решение 1: Самый эффективный способ — метод Загрузить()

Одним из наиболее простых, производительных и распространенных способов заполнения табличной части документа является использование метода Загрузить(). Этот метод позволяет загрузить все строки или часть строк из другой таблицы значений или массива структур напрямую в табличную часть документа. Он значительно сокращает объем кода и повышает производительность, особенно при большом количестве строк.

Для успешного использования метода Загрузить() необходимо, чтобы имена колонок в вашей исходной таблице значений (в нашем случае это Т, результат выполнения запроса) совпадали с именами реквизитов табличной части документа. Если имена совпадают, задача решается одной строкой кода.

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

Давайте посмотрим на пример:


// Исходный запрос автора
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
               |    ТаблицаВодителиВодители.Ссылка,
               |    ТаблицаВодителиВодители.Ссылка.Дата,
               |    ТаблицаВодителиВодители.Маршрут КАК Маршрут,
               |    ТаблицаВодителиВодители.Автомобиль КАК Автомобиль,
               |    ТаблицаВодителиВодители.Водитель КАК Водитель,
               |    ЕСТЬNULL(ТаблицаВодителиВодители.ПробегЗаДень, 0) КАК КМ,
               |    ТаблицаВодителиВодители.ТемператураВХолодильнике КАК Температура,
               |    ЕСТЬNULL(ТаблицаВодителиВодители.ТопливоГрн, 0) КАК Топливо,
               |    ЕСТЬNULL(ТаблицаВодителиВодители.СкоростьГрн, 0) КАК Скорость,
               |    ЕСТЬNULL(ТаблицаВодителиВодители.Штраф, """") КАК Штраф
               |ИЗ
               |    Документ.ТаблицаВодители.Водители КАК ТаблицаВодителиВодители
               |ГДЕ
               |    ТаблицаВодителиВодители.Ссылка = &СсылкаТВ";

Запрос.УстановитьПараметр("СсылкаТВ", СсылкаТВ);
ТаблицаДанныхЗапроса = Запрос.Выполнить().Выгрузить(); // Получаем ТаблицуЗначений

// Предположим, что "Объект" - это наш документ, а "Маршруты" - его табличная часть
// 1. Очищаем табличную часть перед заполнением
Объект.Маршруты.Очистить(); 

// 2. Загружаем данные из ТаблицыДанныхЗапроса в табличную часть
Объект.Маршруты.Загрузить(ТаблицаДанныхЗапроса);

// Если вам нужно выполнить дополнительные расчеты, как в исходном коде автора, 
// это можно сделать после загрузки данных, или еще лучше - в самом запросе.
// Например, пройдемся по уже заполненной табличной части для расчета стоимости
Для Каждого СтрТЧ Из Объект.Маршруты Цикл
    // Предполагаем, что ЗарплатаВодителю и ЗарплатаГрузчику уже как-то определены
    // или являются реквизитами табличной части
    СтрТЧ.Стоимость = СтрТЧ.ЗарплатаВодителю + СтрТЧ.ЗарплатаГрузчику + СтрТЧ.Скорость + СтрТЧ.Топливо;
КонецЦикла;

Особенности использования метода Загрузить() с соответствием колонок:

Если имена колонок в вашей таблице значений отличаются от имен реквизитов табличной части документа, вы можете использовать перегруженный вариант метода Загрузить(), который принимает параметр СоответствиеКолонок. Этот параметр представляет собой структуру, где ключ структуры — это имя колонки в исходной таблице значений, а значение структуры — это имя реквизита в табличной части документа.

Предположим, в запросе колонка называется МаршрутЗаДень, а в табличной части реквизит — МаршрутДоставки. Тогда соответствие будет выглядеть так:


// Создаем структуру соответствия колонок
Соответствие = Новый Структура();
Соответствие.Вставить("МаршрутЗаДень", "МаршрутДоставки");
Соответствие.Вставить("Автомобиль", "Машины"); // Если в запросе "Автомобиль", а в ТЧ "Машины"
// Добавляем все необходимые соответствия

Объект.Маршруты.Очистить();
Объект.Маршруты.Загрузить(ТаблицаДанныхЗапроса, Соответствие);

Когда использовать: Этот метод идеален для ситуаций, когда необходимо перенести большой объем данных без сложной дополнительной логики или преобразований для каждой строки. Это самый производительный способ массового заполнения.

Решение 2: Построчное заполнение с Добавить() и ЗаполнитьЗначенияСвойств()

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

Давайте разберем шаги:

  1. Очистка табличной части: Так же, как и в предыдущем методе, если табличная часть должна быть заполнена с нуля, мы ее очищаем.
  2. Перебор строк исходной таблицы значений: Мы организуем цикл по строкам таблицы значений, полученной из запроса (ТаблицаДанныхЗапроса).
  3. Добавление новой строки в табличную часть документа: Внутри цикла для каждой строки из ТаблицаДанныхЗапроса мы добавляем новую пустую строку в табличную часть документа, используя метод Добавить(). Этот метод возвращает ссылку на вновь созданную строку табличной части.
  4. Заполнение свойств новой строки: У нас есть два основных способа заполнить реквизиты новой строки:
    • Метод ЗаполнитьЗначенияСвойств(): Это очень удобный метод, который копирует значения одноименных свойств из одного объекта в другой. Если имена колонок текущей строки таблицы значений совпадают с именами реквизитов новой строки табличной части, мы можем использовать его.
    • Ручное присвоение: Если имена не совпадают или требуются дополнительные вычисления/преобразования, мы вручную присваиваем значения каждому реквизиту.

Посмотрим на пример, используя подход с Добавить() и ЗаполнитьЗначенияСвойств():


// Исходный запрос остается тем же
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
               |    ТаблицаВодителиВодители.Ссылка,
               |    ТаблицаВодителиВодители.Ссылка.Дата,
               |    ТаблицаВодителиВодители.Маршрут КАК Маршрут,
               |    ТаблицаВодителиВодители.Автомобиль КАК Автомобиль,
               |    ТаблицаВодителиВодители.Водитель КАК Водитель,
               |    ЕСТЬNULL(ТаблицаВодителиВодители.ПробегЗаДень, 0) КАК КМ,
               |    ТаблицаВодителиВодители.ТемператураВХолодильнике КАК Температура,
               |    ЕСТЬNULL(ТаблицаВодителиВодители.ТопливоГрн, 0) КАК Топливо,
               |    ЕСТЬNULL(ТаблицаВодителиВодители.СкоростьГрн, 0) КАК Скорость,
               |    ЕСТЬNULL(ТаблицаВодителиВодители.Штраф, """") КАК Штраф
               |ИЗ
               |    Документ.ТаблицаВодители.Водители КАК ТаблицаВодителиВодители
               |ГДЕ
               |    ТаблицаВодителиВодители.Ссылка = &СсылкаТВ";

Запрос.УстановитьПараметр("СсылкаТВ", СсылкаТВ);
ТаблицаДанныхЗапроса = Запрос.Выполнить().Выгрузить(); 

// 1. Очищаем табличную часть документа
Объект.Маршруты.Очистить(); 

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

    // 4. Заполняем свойства новой строки с помощью ЗаполнитьЗначенияСвойств()
    // Этот метод скопирует все свойства из СтрокаТЗ в НоваяСтрокаТЧ,
    // имена которых совпадают.
    ЗаполнитьЗначенияСвойств(НоваяСтрокаТЧ, СтрокаТЗ);

    // 5. Если есть дополнительные расчеты или реквизиты с отличающимися именами, 
    // заполняем их вручную.
    // Например, в примере автора было: Стр.Машины = Т.Автомобиль; 
    // Если в ТЗ колонка "Автомобиль", а в ТЧ реквизит "Машины", то делаем так:
    // НоваяСтрокаТЧ.Машины = СтрокаТЗ.Автомобиль; 

    // Расчет поля "Стоимость", как в примере автора
    // Предполагаем, что ЗарплатаВодителю и ЗарплатаГрузчику либо есть в ТЗ,
    // либо их значения берутся откуда-то еще, или являются реквизитами самой ТЧ.
    // Если их нет в ТЗ, то нам нужно их получить для расчета.
    // Для примера предположим, что они есть как реквизиты ТЧ и их нужно заполнить
    // или они уже заполнены/рассчитаны.
    // Если их нет нигде, а нужно рассчитать, то это уже более сложная логика.
    // Для простоты примера:
    НоваяСтрокаТЧ.Стоимость = 0; // Инициализируем
    Если СтрокаТЗ.Свойство("ЗарплатаВодителю") Тогда
        НоваяСтрокаТЧ.ЗарплатаВодителю = СтрокаТЗ.ЗарплатаВодителю;
    КонецЕсли;
    Если СтрокаТЗ.Свойство("ЗарплатаГрузчику") Тогда
        НоваяСтрокаТЧ.ЗарплатаГрузчику = СтрокаТЗ.ЗарплатаГрузчику;
    КонецЕсли;

    // После заполнения всех полей из ТЗ и дополнительных расчетов
    НоваяСтрокаТЧ.Стоимость = НоваяСтрокаТЧ.ЗарплатаВодителю + НоваяСтрокаТЧ.ЗарплатаГрузчику 
                              + НоваяСтрокаТЧ.Скорость + НоваяСтрокаТЧ.Топливо;

КонецЦикла;

Когда использовать: Этот метод дает большую гибкость. Используйте его, если вам нужен детальный контроль над процессом добавления каждой строки, если требуется выполнять вычисления, применять сложную логику фильтрации, или если существует необходимость в адаптации данных перед их записью в табличную часть. Он менее производителен, чем Загрузить() при больших объемах данных, но более гибок.

Решение 3: Поиск и обновление существующих строк (с помощью НайтиСтроки())

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

Метод НайтиСтроки() доступен для таблиц значений и табличных частей и позволяет найти все строки, которые соответствуют заданным условиям отбора. Условия отбора задаются в виде структуры, где ключ структуры — это имя колонки/реквизита, а значение — искомое значение.

Давайте рассмотрим сценарий: мы хотим обновить уже существующие маршруты в табличной части документа, а если маршрута нет — добавить его. Это более сложная логика, но НайтиСтроки() здесь может помочь.


// Предположим, что ТаблицаДанныхЗапроса уже получена.
// Мы хотим пройтись по данным из запроса и либо обновить существующую строку в ТЧ "Маршруты",
// либо добавить новую.

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

    НайденныеСтрокиТЧ = Объект.Маршруты.НайтиСтроки(Отбор);

    Если НайденныеСтрокиТЧ.Количество() > 0 Тогда
        // Если нашли существующую строку, обновляем ее
        СтрокаТЧДляОбновления = НайденныеСтрокиТЧ[0]; // Берем первую найденную строку
        ЗаполнитьЗначенияСвойств(СтрокаТЧДляОбновления, СтрокаИзЗапроса);
        // Дополнительные расчеты или присвоения
        СтрокаТЧДляОбновления.Стоимость = СтрокаТЧДляОбновления.ЗарплатаВодителю + СтрокаТЧДляОбновления.ЗарплатаГрузчику 
                                        + СтрокаТЧДляОбновления.Скорость + СтрокаТЧДляОбновления.Топливо;
    Иначе
        // Если строка не найдена, добавляем новую
        НоваяСтрокаТЧ = Объект.Маршруты.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрокаТЧ, СтрокаИзЗапроса);
        // Дополнительные расчеты или присвоения для новой строки
        НоваяСтрокаТЧ.Стоимость = НоваяСтрокаТЧ.ЗарплатаВодителю + НоваяСтрокаТЧ.ЗарплатаГрузчику 
                                + НоваяСтрокаТЧ.Скорость + НоваяСтрокаТЧ.Топливо;
    КонецЕсли;
КонецЦикла;

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

Дополнительные рекомендации и лучшие практики

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

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

Применяя эти методы и рекомендации, вы сможете эффективно и корректно заполнять табличные части документов в 1С:Предприятие 8.3, делая ваш код более надежным, производительным и легким для поддержки.

← На главную