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

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

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

Постановка задачи

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

Решение 1: Иерархический обход результата запроса с итогами

Этот метод был выбран автором темы как наиболее стабильный и быстрый для объема в 3000 сотрудников и 30 показателей (обработка занимает около 2 секунд). Суть заключается в использовании предложения ИТОГИ ПО в тексте запроса.

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


// Пример текста запроса
ТекстЗапроса = "ВЫБРАТЬ
               |    Сотрудники.Сотрудник КАК Сотрудник,
               |    Сотрудники.Подразделение КАК Подразделение,
               |    Сотрудники.Должность КАК Должность,
               |    АдаптацияЗначенияПоказателей.ПоказательАдаптации КАК Показатель,
               |    АдаптацияЗначенияПоказателей.Значение КАК Значение
               |ИЗ
               |    ВТСотрудники КАК Сотрудники
               |ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.АдаптацияЗначенияПоказателей КАК АдаптацияЗначенияПоказателей
               |    ПО Сотрудники.Сотрудник = АдаптацияЗначенияПоказателей.Сотрудник
               |ИТОГИ ПО
               |    Сотрудник";

РезультатЗапроса = Запрос.Выполнить();

Проанализируем алгоритм обхода. Для эффективного заполнения ТаблицаЗначений мы используем метод выборки ПоГруппировкамСИерархией. На верхнем уровне (группировка по сотруднику) мы добавляем новую строку в таблицу. На нижнем уровне (детальные записи) мы заполняем колонки-показатели в уже созданной строке.


ВыборкаСотрудник = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

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

Решение 2: Транспонирование через «Выбор Когда» (Case When)

Если количество показателей фиксировано или заранее известно, можно избежать иерархического обхода и множественных соединений, построив «плоский» запрос. Вместо 33 левых соединений (LEFT JOIN) мы используем агрегатные функции с условием. Это значительно снижает нагрузку на SQL-сервер.

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


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

ТекстЗапроса = "ВЫБРАТЬ Сотрудник" + ТекстПолей + " ИЗ РегистрСведений.АдаптацияЗначенияПоказателей СГРУППИРОВАТЬ ПО Сотрудник";

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

Решение 3: Оптимизация заполнения через объект «Соответствие» (Map)

Если данные из регистра и данные сотрудников приходят из разных источников или сложных функций, часто программисты используют поиск в таблице методом Найти(). При объеме в 90 000 ячеек (3000 строк на 30 колонок) обычный поиск будет работать крайне медленно, так как он имеет сложность O(n).

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

  1. Сначала создаем пустые строки в ТаблицаЗначений для всех сотрудников.
  2. Параллельно заполняем Соответствие: КартаСтрок.Вставить(СотрудникСсылка, НоваяСтрока).
  3. При чтении данных показателей из регистра мы моментально получаем нужную строку: ТекущаяСтрока = КартаСтрок[Выборка.Сотрудник].

Доступ к данным в Соответствие происходит практически мгновенно (сложность O(1)), что позволяет заполнять огромные таблицы за доли секунды.

Важные рекомендации по работе с кадровыми данными

При построении подобных таблиц, особенно в конфигурациях типа ЗУП 3.1, важно помнить о периодичности данных. Подразделение и должность сотрудника — это значения, меняющиеся во времени. Рассмотрим несколько советов:

Метод «Свернуть» для ленивого построения

Проанализируем еще один подход, который требует минимум кода, но больше оперативной памяти. Мы можем выгрузить все данные из регистра в одну «длинную» таблицу, где будут колонки: Сотрудник, Подразделение, Должность и все возможные колонки-показатели. В каждой такой строке будет заполнена только одна колонка с показателем, остальные будут пустыми.

После заполнения вызываем метод ТаблицаРезультат.Свернуть("Сотрудник, Подразделение, Должность", "Показатель1, Показатель2, ... Показатель30"). Платформа самостоятельно схлопнет строки по сотруднику, просуммировав значения показателей. Поскольку в каждой исходной строке значение было только в одной колонке, мы получим идеально заполненную таблицу.

← На главную