Как динамически сложить значения нескольких колонок в таблице значений 1С и записать их в документ?

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

При разработке обработок загрузки данных, интеграционных механизмов или сложных аналитических отчетов программисты 1С часто сталкиваются с ситуацией, когда структура входящей таблицы значений заранее неизвестна или может меняться. Например, при парсинге файлов Excel или получении данных из внешних API имена колонок могут генерироваться динамически (_1, _2, ... _10 и так далее). Если нам требуется сложить показатели из нескольких таких динамических колонок, имя которых мы узнаем только в момент выполнения кода, стандартное обращение через точку (Строка.ИмяКолонки) не сработает.

В этой статье мы подробно разберем, как динамически обойти строку таблицы значений, сложить данные из колонок, имена которых хранятся в отдельном массиве, и записать полученный результат в табличную часть документа. Также мы проанализируем частые ошибки при работе с коллекциями, рассмотрим вопросы оптимизации производительности и затронем тему трансформации таблиц (аналоги PIVOT и UNPIVOT в 1С).

Разбор типичной ошибки при поиске колонок

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


СтрокаСебест = тз.Найти("Себестоимость ");
Если СтрокаСебест <> Неопределено Тогда
    Для Каждого стрсебес Из СтрокаСебест Цикл
        // Попытка обойти свойства строки в цикле
    КонецЦикла;
КонецЕсли;

Этот код содержит критическую ошибку, которая приведет к аварийному завершению работы программы с ошибкой "Итератор для значения не определен". Выясним причину:

  1. Метод ТаблицаЗначений.Найти() возвращает ровно один объект типа СтрокаТаблицыЗначений (первую найденную строку, удовлетворяющую условию поиска) либо значение Неопределено.
  2. Объект СтрокаТаблицыЗначений сам по себе не является коллекцией, его нельзя обходить циклом Для каждого.
  3. Если задача состоит в том, чтобы найти именно колонки, содержащие ключевое слово в заголовке или имени, необходимо обходить коллекцию тз.Колонки, а не строки таблицы.

Давайте посмотрим, как правильно и динамически собрать массив имен нужных колонок:


МассивКолонок = Новый Массив;
Для Каждого Колонка Из тз.Колонки Цикл
    // Проверяем, содержит ли имя или заголовок колонки нужное слово
    Если Найти(ВРег(Колонка.Заголовок), "СЕБЕСТОИМОСТЬ") > 0 Тогда
        МассивКолонок.Добавить(Колонка.Имя);
    КонецЕсли;
КонецЦикла;

Решение задачи: Динамический доступ к свойствам строки по имени

Представим, что мы успешно заполнили МассивКолонок именами колонок, значения которых нам нужно сложить (например, _12 и _13). Теперь нам нужно перебрать строки нашей таблицы значений, сложить показатели из этих колонок для каждой строки и перенести результат в табличную часть создаваемого документа.

Для обращения к свойству (колонке) строки таблицы значений по строковому имени в платформе 1С используется синтаксис квадратных скобок: СтрокаТаблицы[ИмяКолонки]. Это позволяет динамически вычислять имя свойства прямо во время выполнения кода.

Разберем по шагам итоговое рабочее решение:


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

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

Оптимизация производительности: Доступ по индексу колонки

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

Для оптимизации процесса мы можем предварительно получить числовые индексы колонок и обращаться к значениям строки по индексу (Стр[ИндексКолонки]). Поиск по числовому индексу в оперативной памяти происходит значительно быстрее.

Проанализируем оптимизированный вариант алгоритма:


// Шаг 1: Заранее определяем числовые индексы колонок
МассивИндексов = Новый Массив;
Для Каждого ИмяКолонки Из МассивКолонок Цикл
    КолонкаОбъект = тз.Колонки.Найти(ИмяКолонки);
    Если КолонкаОбъект <> Неопределено Тогда
        ИндексКолонки = тз.Колонки.Индекс(КолонкаОбъект);
        МассивИндексов.Добавить(ИндексКолонки);
    КонецЕсли;
КонецЦикла;

// Шаг 2: Выполняем обход строк таблицы с использованием индексов
Для Каждого Стр Из тз Цикл
    НоменСсылка = Справочники.Номенклатура.НайтиПоНаименованию(Стр._2, Истина);
    
    ТЧНов = НовыйДок.Товары.Добавить();
    ТЧНов.Номенклатура = НоменСсылка;
    
    Себестоимость = 0;
    // Быстрый доступ к значению строки по числовому индексу
    Для Каждого ИндексКолонки Из МассивИндексов Цикл
        Себестоимость = Себестоимость + Стр[ИндексКолонки];
    КонецЦикла;
    
    ТЧНов.Себестоимость = Себестоимость;

Альтернативные методы суммирования

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


ОбщийИтог = 0;
Для Каждого ИмяКолонки Из МассивКолонок Цикл
    ОбщийИтог = ОбщийИтог + тз.Итог(ИмяКолонки);
КонецЦикла;

Трансформация таблиц: Эмуляция PIVOT и UNPIVOT в 1С

В процессе обсуждения темы на форуме зашла речь о необходимости преобразования структуры данных (операция UNPIVOT). Наличие в таблице колонок вида _10, _11, _12 указывает на плоскую, денормализованную структуру. Намного удобнее и правильнее работать с нормализованными данными, когда вместо множества колонок мы имеем строки с аналитикой (например: Номенклатура, ВидЗатраты, Сумма).

Платформа 1С не имеет нативной поддержки SQL-операторов PIVOT и UNPIVOT в своем языке запросов, но разработчики могут легко эмулировать их.

Эмуляция UNPIVOT (разворот колонок в строки) выполняется с помощью многократного объединения таблиц через оператор ОБЪЕДИНИТЬ ВСЕ:


ВЫБРАТЬ
    Номенклатура,
    "Прямые затраты" КАК ВидСебестоимости,
    КолонкаПрямыеЗатраты КАК Сумма
ИЗ
    ВТ_ИсходныеДанные

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Номенклатура,
    "Косвенные затраты",
    КолонкаКосвенныеЗатраты
ИЗ
    ВТ_ИсходныеДанные

Эмуляция PIVOT (сведение строк в колонки) реализуется с помощью конструкции условного агрегирования СУММА(ВЫБОР КОГДА...):


ВЫБРАТЬ
    Номенклатура,
    СУММА(ВЫБОР КОГДА ВидСебестоимости = "Прямые затраты" ТОГДА Сумма ИНАЧЕ 0 КОНЕЦ) КАК СебестоимостьПрямые,
    СУММА(ВЫБОР КОГДА ВидСебестоимости = "Косвенные затраты" ТОГДА Сумма ИНАЧЕ 0 КОНЕЦ) КАК СебестоимостьКосвенные
ИЗ
    ВТ_Затраты
СГРУППИРОВАТЬ ПО
    Номенклатура

Интересный факт: Использование СЛАУ в платформе 1С

В финале обсуждения участники форума иронично предложили использовать механизм решения СЛАУ (систем линейных алгебраических уравнений) для сложения двух колонок. Хотя для решения нашей задачи это очевидное избыточное усложнение, сам механизм СЛАУ является реальным инструментом платформы 1С.

Начиная с версии 8.3.14, в платформе появился объект РасчетСистемЛинейныхУравнений. Он был разработан специально для сложных расчетов себестоимости в конфигурациях класса ERP (в рамках распределения затрат по методологии РАУЗ — Расширенной аналитики учета затрат). Когда на производстве возникает встречный выпуск услуг между вспомогательными цехами, затраты образуют сложные циклические взаимосвязи. Математически это описывается системами линейных уравнений с тысячами переменных. Благодаря платформенному механизму СЛАУ, реализованному на оптимизированных библиотеках C++ с поддержкой многопоточности, время расчета себестоимости на крупных предприятиях сократилось с нескольких часов до нескольких минут.

← На главную