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

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

В практике разработчика 1С часто встречается задача, когда данные поступают в «плоском» виде — например, при импорте из текстовых файлов или простых Excel-таблиц. Одной из самых трудоемких ситуаций является случай, когда и названия групп, и названия товаров находятся в одной колонке, а принадлежность товара к группе определяется лишь тем, что он расположен в списке ниже заголовка этой группы. В этой статье мы подробно разберем, как программно обработать такую таблицу и превратить её в структурированное дерево значений или таблицу с иерархией.

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

Представим, что у нас есть объект ТаблицаЗначений с именем Табл и колонкой Кол1. Содержимое выглядит следующим образом:

  1. Группа товара: Подушки
  2. Подушка перьевая
  3. Подушка надувная
  4. Группа товара: Одеяла
  5. Одеяло из овечьей шерсти

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

Решение 1: Формирование дерева значений через обход циклом

Самый прямой и эффективный способ — один раз пройти по таблице циклом и динамически создавать узлы в объекте ДеревоЗначений. Это решение оптимально, так как выполняется за один проход (сложность O(n)).

Проанализируем алгоритм: мы создаем пустое дерево, перебираем строки исходной таблицы и, если находим ключевое слово «Группа», создаем новую ветку. Если ключевого слова нет — добавляем строку в текущую активную ветку.


// Создаем структуру дерева
Дерево = Новый ДеревоЗначений;
Дерево.Колонки.Добавить("Наименование");

Переменная ТекущаяГруппа = Неопределено;

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

Решение 2: Использование вспомогательной колонки «Родитель»

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

Рассмотрим этот метод по шагам:

  1. Добавляем в таблицу новую колонку Родитель.
  2. Проходим циклом и заполняем эту колонку.
  3. Удаляем строки-заголовки, так как информация о них теперь хранится в каждой строке товара.

Табл.Колонки.Добавить("Родитель", Новый ОписаниеТипов("Строка"));
ТекущееИмяГруппы = "";

// Шаг 1: Заполнение признака родителя
Для Каждого СтрокаТЗ Из Табл Цикл
    Если СтрНайти(СтрокаТЗ.Кол1, "Группа товара:") > 0 Тогда
        ТекущееИмяГруппы = СокрЛП(СтрЗаменить(СтрокаТЗ.Кол1, "Группа товара:", ""));
    КонецЕсли;
    СтрокаТЗ.Родитель = ТекущееИмяГруппы;
КонецЦикла;

// Шаг 2: Удаление служебных строк-заголовков
ПараметрыПоиска = Новый Структура("Кол1", "");
// Здесь можно использовать массив для удаления строк, содержащих "Группа товара:"
СтрокиДляУдаления = Новый Массив;
Для Каждого СтрокаТЗ Из Табл Цикл
    Если СтрНайти(СтрокаТЗ.Кол1, "Группа товара:") > 0 Тогда
        СтрокиДляУдаления.Добавить(СтрокаТЗ);
    КонецЕсли;
КонецЦикла;

Для Каждого Стр Из СтрокиДляУдаления Цикл
    Табл.Удалить(Стр);
КонецЦикла;

Оптимизация с использованием современных функций (платформа 8.3)

Проанализируем ситуацию: если слово «Группа» может встретиться в середине названия товара (например, «Подушка для группы детского сада»), метод СтрНайти может сработать ложно. Для повышения надежности в современной платформе 8.3 рекомендуется использовать метод СтрНачинаетсяС.

Посмотрим на пример проверки:


Если СтрНачинаетсяС(СокрЛП(СтрокаТЗ.Кол1), "Группа товара:") Тогда
    // Это гарантированно начало строки, а не случайное совпадение в середине
КонецЕсли;

Применение регулярных выражений (RegExp)

Если признак группы более сложный (например, «Группа №1:», «Группа №2:»), лучше использовать регулярные выражения. Это позволит гибко настраивать шаблоны поиска без жесткой привязки к конкретному тексту.

Разберем пример реализации через COM-объект:


RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина;
RegExp.Global = Ложь;
RegExp.Pattern = "^Группа товара:\s*(.*)"; // Ищем в начале строки

Для Каждого СтрокаТЗ Из Табл Цикл
    Matches = RegExp.Execute(СтрокаТЗ.Кол1);
    Если Matches.Count > 0 Тогда
        // Это группа. Извлекаем захваченную подстроку с названием
        ИмяГруппы = Matches.Item(0).SubMatches.Item(0);
        // Далее логика добавления в дерево...
    КонецЕсли;
КонецЦикла;

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

При обработке очень больших объемов данных (десятки тысяч строк) важно учитывать следующие моменты:

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

← На главную