Как динамически заполнить колонки в таблице значений 1С 8.3

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

При разработке отчетов и обработок в среде 1С часто возникает задача транспонирования данных — разворота строк таблицы в колонки. Типичный пример: имеется исходная таблица значений (ТЗ) со списком сотрудников, статьями финансирования и начисленными суммами. Чтобы сделать таблицу легкочитаемой, статьи финансирования необходимо «развернуть» по горизонтали, превратив их в динамические колонки, и распределить по ним соответствующие суммы. В этой статье мы подробно, шаг за шагом, разберем, как решить эту задачу на платформе 1С:Предприятие 8.3.

В чем заключается основная сложность?

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

Для обхода этого ограничения в платформе 1С предусмотрен специальный синтаксис — обращение к свойствам объекта через квадратные скобки: СтрокаТЗ[ИмяПеременнойКолонки]. Внутри скобок передается строковая переменная, содержащая реальное имя колонки, созданной в ТЗ.

---

Метод 1. Использование соответствия (Map) для распределения сумм

Рассмотрим классический и наиболее производительный алгоритм разворота строк в колонки с использованием объекта Соответствие. Этот объект позволит нам быстро сопоставлять элементы справочника (статьи финансирования) с именами динамически созданных колонок таблицы значений.

Выполним задачу по шагам:

  1. Создадим новую (итоговую) таблицу значений, куда перенесем ключевые разрезы (например, сотрудник или документ) и добавим динамические колонки.
  2. Инициализируем объект Соответствие, в котором ключом будет ссылка на статью финансирования, а значением — строковое имя созданной колонки.
  3. Обойдем исходную таблицу значений в цикле, заполняя итоговую таблицу и распределяя суммы.

Посмотрим на практический пример реализации этого алгоритма:


// Функция выполняет свертку исходной таблицы и разворот статей финансирования в колонки
Процедура РаспределитьСуммыПоСтатьям(ИсходнаяТЗ)
    
    // 1. Создаем итоговую таблицу значений
    ИтоговаяТЗ = Новый ТаблицаЗначений;
    ИтоговаяТЗ.Колонки.Добавить("Сотрудник", Новый ОписаниеТипов("СправочникСсылка.Сотрудники"));
    ИтоговаяТЗ.Колонки.Добавить("ОбщаяСумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    // Соответствие для быстрой связи: СтатьяСсылка -> ИмяКолонки
    СоответствиеКолонок = Новый Соответствие;
    
    // 2. Сначала соберем все уникальные статьи финансирования, чтобы создать колонки
    ТаблицаСтатей = ИсходнаяТЗ.Скопировать();
    ТаблицаСтатей.Свернуть("СтатьяФинансирования");
    
    Для Каждого СтрСтатья Из ТаблицаСтатей Цикл
        
        СсылкаНаСтатью = СтрСтатья.СтатьяФинансирования;
        Если Не ЗначениеЗаполнено(СсылкаНаСтатью) Тогда
            Продолжить;
        КонецЕсли;
        
        // Преобразуем наименование статьи в валидный идентификатор для имени колонки
        ИмяНовойКолонки = ПреобразоватьВИдентификаторКолонки(СсылкаНаСтатью.Наименование);
        
        // Добавляем колонку в итоговую ТЗ, если её еще нет
        Если ИтоговаяТЗ.Колонки.Найти(ИмяНовойКолонки) = Неопределено Тогда
            ИтоговаяТЗ.Колонки.Добавить(ИмяНовойКолонки, Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
        КонецЕсли;
        
        // Запоминаем связь статьи и колонки
        СоответствиеКолонок.Вставить(СсылкаНаСтатью, ИмяНовойКолонки);
        
    КонецЦикла;
    
    // 3. Заполняем итоговую таблицу данными и распределяем суммы
    Для Каждого СтрокаИсходная Из ИсходнаяТЗ Цикл
        
        // Ищем уже существующую строку по сотруднику в итоговой таблице
        ПараметрыПоиска = Новый Структура("Сотрудник", СтрокаИсходная.Сотрудник);
        НайденныеСтроки = ИтоговаяТЗ.НайтиСтроки(ПараметрыПоиска);
        
        Если НайденныеСтроки.Количество() > 0 Тогда
            СтрокаПолучатель = НайденныеСтроки[0];
        Иначе
            СтрокаПолучатель = ИтоговаяТЗ.Добавить();
            СтрокаПолучатель.Сотрудник = СтрокаИсходная.Сотрудник;
        КонецЕсли;
        
        // Суммируем общий итог по строке
        СтрокаПолучатель.ОбщаяСумма = СтрокаПолучатель.ОбщаяСумма + СтрокаИсходная.Сумма;
        
        // Определяем имя динамической колонки по статье финансирования
        ИмяКолонкиДляСуммы = СоответствиеКолонок.Получить(СтрокаИсходная.СтатьяФинансирования);
        
        Если ИмяКолонкиДляСуммы <> Неопределено Тогда
            // Самый важный момент: динамическое обращение через квадратные скобки
            СтрокаПолучатель[ИмяКолонкиДляСуммы] = СтрокаПолучатель[ИмяКолонкиДляСуммы] + СтрокаИсходная.Сумма;
        КонецЕсли;
        
    КонецЦикла;
    
    // ИтоговаяТЗ готова для дальнейшего использования или вывода на форму
    
КонецПроцедуры

---

Метод 2. Тонкости нормализации имен колонок (Идентификаторов)

Важным нюансом при создании колонок является правильное формирование их имен. Имя колонки в 1С должно отвечать правилам создания идентификаторов переменной: оно не может содержать пробелов, спецсимволов, знаков препинания и не может начинаться с цифры.

Проанализируем функцию очистки строк, которая преобразует произвольное наименование статьи финансирования в безопасный идентификатор 1С:


&НаСервере
Функция ПреобразоватьВИдентификаторКолонки(Знач Наименование)
    
    // Удаляем все недопустимые символы
    Наименование = СтрЗаменить(Наименование, " ", "_");
    Наименование = СтрЗаменить(Наименование, "-", "_");
    Наименование = СтрЗаменить(Наименование, "/", "_");
    Наименование = СтрЗаменить(Наименование, "\", "_");
    Наименование = СтрЗаменить(Наименование, "(", "");
    Наименование = СтрЗаменить(Наименование, ")", "");
    Наименование = СтрЗаменить(Наименование, "%", "");
    Наименование = СтрЗаменить(Наименование, "№", "Номер");
    
    // Если наименование начинается с цифры, добавим префикс
    ПервыйСимвол = Лев(Наименование, 1);
    Если Найти("0123456789", ПервыйСимвол) > 0 Тогда
        Наименование = "Статья_" + Наименование;
    КонецЕсли;
    
    Возврат Наименование;
    
КонецФункции

Важный совет: Если в вашей базе данных возможна ситуация, когда две разные статьи финансирования после очистки спецсимволов преобразуются в одно и то же имя (например, «Статья - 1» и «Статья / 1» станут Статья__1), обязательно добавьте в алгоритм проверку уникальности имен колонок. При совпадении имен добавляйте к идентификатору цифровой постфикс (_1, _2 и так далее).

---

Метод 3. Программный вывод динамической ТЗ на управляемую форму

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

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


&НаСервере
Процедура ОтобразитьТЗНаФорме(ИтоговаяТЗ)
    
    // 1. Очищаем старые динамические элементы и реквизиты формы, если они были созданы ранее
    МассивУдаляемыхРеквизитов = Новый Массив;
    Для Каждого РеквизитФормы Из ЭтаФорма.ПолучитьРеквизиты("ДинамическаяТаблицаДанных") Цикл
        // Пропускаем основные статические колонки, удаляем только динамические
        Если РеквизитФормы.Имя <> "Сотрудник" И РеквизитФормы.Имя <> "ОбщаяСумма" Тогда
            МассивУдаляемыхРеквизитов.Добавить("ДинамическаяТаблицаДанных." + РеквизитФормы.Имя);
            
            // Удаляем визуальный элемент управления с формы
            ЭлементФормы = Элементы.Найти("ДинамическаяТаблицаДанных" + РеквизитФормы.Имя);
            Если ЭлементФормы <> Неопределено Тогда
                Элементы.Удалить(ЭлементФормы);
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    
    Если МассивУдаляемыхРеквизитов.Количество() > 0 Тогда
        ЭтаФорма.ИзменитьРеквизиты(, МассивУдаляемыхРеквизитов);
    КонецЕсли;
    
    // 2. Создаем массив новых реквизитов для добавления
    МассивДобавляемыхРеквизитов = Новый Массив;
    Для Каждого КолонкаТЗ Из ИтоговаяТЗ.Колонки  Цикл
        Если КолонкаТЗ.Имя = "Сотрудник" Или КолонкаТЗ.Имя = "ОбщаяСумма" Тогда
            Продолжить; // Они уже созданы на форме статически
        КонецЕсли;
        
        // Создаем новый реквизит формы внутри нашей таблицы "ДинамическаяТаблицаДанных"
        НовыйРеквизит = Новый РеквизитФормы(КолонкаТЗ.Имя, КолонкаТЗ.ТипЗначения, "ДинамическаяТаблицаДанных", КолонкаТЗ.Заголовок);
        МассивДобавляемыхРеквизитов.Добавить(НовыйРеквизит);
    КонецЦикла;
    
    // Применяем изменения структуры реквизитов формы
    ЭтаФорма.ИзменитьРеквизиты(МассивДобавляемыхРеквизитов);
    
    // 3. Создаем визуальные элементы (колонки) в таблице на форме
    РодительТаблица = Элементы.ДинамическаяТаблицаДанных;
    
    Для Каждого Реквизит Из МассивДобавляемыхРеквизитов Цикл
        НовыйЭлемент = Элементы.Добавить("ДинамическаяТаблицаДанных" + Реквизит.Имя, Тип("ПолеФормы"), РодительТаблица);
        НовыйЭлемент.Вид = ВидПоляФормы.ПолеВвода;
        НовыйЭлемент.ПутьКДанным = "ДинамическаяТаблицаДанных." + Реквизит.Имя;
        НовыйЭлемент.ПоложениеЗаголовка = ПоложениеЗаголовкаЭлементаФормы.Нет;
    КонецЦикла;
    
    // 4. Копируем данные из нашей заполненной ТЗ в реквизит формы
    ЗначениеВРеквизитФормы(ИтоговаяТЗ, "ДинамическаяТаблицаДанных");
    
КонецПроцедуры

---

Метод 4. Использование Системы компоновки данных (СКД) без отчета

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

Преимущество этого метода в том, что платформа 1С берет на себя всю логику группировки по строкам, динамическое создание колонок кросс-таблицы и расчет итогов по вертикали и горизонтали.

Разберем по шагам логику этого подхода в программном коде:


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

Процедура ДобавитьПолеВНабор(НаборДанных, ИмяПоля, ПутьКПолю, ТипПоля)
    Поле = НаборДанных.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
    Поле.Имя = ИмяПоля;
    Поле.ПутьКПолю = ПутьКПолю;
    Поле.Заголовок = ИмяПоля;
    Поле.ТипЗначения = ТипПоля;
КонецПроцедуры

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

---

Резюме и рекомендации

Мы проанализировали несколько различных подходов к заполнению динамических колонок в ТЗ на платформе 1С 8.3:

← На главную