В платформе 1С:Предприятие 7.7 работа с Таблицей Значений (ТЗ) зачастую требует ручных манипуляций, особенно когда необходимо вывести плоский список данных в виде иерархического дерева (например, с группировками товаров). Стандартная ТЗ не умеет автоматически «подтягивать» родителей, если они не были добавлены изначально. Рассмотрим несколько эффективных способов решения этой задачи, от оптимизации запросов до рекурсивных алгоритмов.
Самый правильный и производительный метод — получить иерархию сразу на этапе выборки данных. Если вы заполняете ТЗ из регистров, использование объекта Запрос позволяет задействовать встроенную возможность обхода группировок с учетом иерархии справочника.
Проанализируем ситуацию: часто разработчики пытаются сначала выбрать движения регистра через ВыбратьДвижения, а затем отдельно получить остатки. Это порождает «плоскую» таблицу. Вместо этого мы можем использовать конструкцию Когда в функциях запроса. Это позволит разделить приходы по разным видам документов прямо внутри одного запроса, сохраняя при этом связь с иерархией справочника.
Рассмотрим пример кода, который объединяет получение данных по разным видам документов и иерархию:
ТекстЗапроса = "
|Период с Дата1 по Дата2;
|МестоХранения = Регистр.Остатки.МестоХранения;
|Номенклатура = Регистр.Остатки.ТМЦ;
|Кол = Регистр.Остатки.ОстатокТовара;
|ТекДок = Регистр.Остатки.ТекущийДокумент;
|Функция НачКол = НачОст(Кол);
|Функция ПрихВсего = Приход(Кол);
|Функция ПрихИзлишек = Приход(Кол) Когда (ТекДок.Вид = ""ПриходИзлишков"");
|Функция РасхВсего = Расход(Кол);
|Функция КонКол = КонОст(Кол);
|Группировка Номенклатура С Группами;
|";
Обратим внимание на ключевой момент: использование параметра С Группами в описании группировки Номенклатура. При обходе такого запроса методом Запрос.Группировка(1), система будет последовательно возвращать сначала папки, а затем вложенные в них элементы. Это позволяет выводить данные в печатную форму в правильном иерархическом порядке без дополнительных манипуляций с ТЗ.
Если данные в Таблицу Значений уже попали из внешних источников или сложной обработки, и переписать всё на один запрос невозможно, нам придется «достроить» дерево вручную. Для этого мы создадим процедуру, которая для каждого элемента будет проверять наличие родителя и добавлять его в таблицу, если его там еще нет.
Разберем, как это реализовать с помощью рекурсии. Нам понадобится основная ТЗ (сделаем её глобальной или передадим по ссылке) и функция, которая «поднимается» вверх по дереву справочника.
Процедура ДобавитьРодителей(ТекНоменклатура, ТЗ)
Если ТекНоменклатура.Родитель.Выбран() = 1 Тогда
// Рекурсивно вызываем для родителя родителя
ДобавитьРодителей(ТекНоменклатура.Родитель, ТЗ);
// Проверяем, нет ли уже этой группы в нашей таблице
НомерСтроки = 0;
Если ТЗ.НайтиЗначение(ТекНоменклатура.Родитель, НомерСтроки, "Товар") = 0 Тогда
ТЗ.НоваяСтрока();
ТЗ.Товар = ТекНоменклатура.Родитель;
// Здесь можно пометить, что это группа, чтобы в печати выделить жирным
КонецЕсли;
КонецЕсли;
КонецПроцедуры
После наполнения ТЗ всеми родителями, её необходимо свернуть и отсортировать. Однако помните, что обычная сортировка ТЗ.Сортировать("Товар") отсортирует всё по алфавиту, что может нарушить вложенность. Для корректной визуализации лучше использовать специализированную сортировку.
Существует интересный алгоритм, позволяющий выстроить строки ТЗ ровно в том порядке, в котором они идут в самом справочнике 1С. Суть метода заключается в создании вспомогательной таблицы, которая нумерует все элементы справочника при полном обходе.
Выясним, как работает этот алгоритм по шагам:
ТЗПор, где будут две колонки: Элемент и Позиция.ВыбратьЭлементы(). В 1С 7.7 этот метод по умолчанию идет по иерархии.НайтиЗначение.Посмотрим на пример реализации этой процедуры:
Процедура СортироватьПоИерархии(ТЗ, ИмяКолонкиТовар, ТипСправочника)
ТЗПор = СоздатьОбъект("ТаблицаЗначений");
ТЗПор.НоваяКолонка("Элемент");
ТЗПор.НоваяКолонка("Позиция");
Спр = СоздатьОбъект("Справочник." + ТипСправочника);
к = 0;
Спр.ВыбратьЭлементы(); // Выборка в иерархическом порядке
Пока Спр.ПолучитьЭлемент() = 1 Цикл
ТЗПор.НоваяСтрока();
ТЗПор.Элемент = Спр.ТекущийЭлемент();
ТЗПор.Позиция = к;
к = к + 1;
КонецЦикла;
ТЗ.НоваяКолонка("СлужебнаяСортировка");
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Стр = 0;
Если ТЗПор.НайтиЗначение(ТЗ.ПолучитьЗначение(ТЗ.НомерСтроки, ИмяКолонкиТовар), Стр, "Элемент") = 1 Тогда
ТЗ.УстановитьЗначение(ТЗ.НомерСтроки, "СлужебнаяСортировка", ТЗПор.ПолучитьЗначение(Стр, "Позиция"));
КонецЕсли;
КонецЦикла;
ТЗ.Сортировать("СлужебнаяСортировка");
ТЗ.УдалитьКолонку("СлужебнаяСортировка");
КонецПроцедуры
Просто отсортировать таблицу недостаточно для красивого отчета. Чтобы пользователь видел дерево, нам нужно добавить отступы. Для этого в шаблоне печатной формы в ячейке наименования товара можно использовать метод Уровень().
Проанализируем ситуацию: если мы выводим элемент, его уровень может быть 1, 2, 3 и т.д. Мы можем программно добавлять пробелы перед названием:
// В коде вывода секции
Отступ = "";
Для н = 1 по ТЗ.Товар.Уровень() - 1 Цикл
Отступ = Отступ + " "; // 4 пробела для каждого уровня
КонецЦикла;
ПечТовар = Отступ + СокрЛП(ТЗ.Товар.Наименование);
Важный совет по производительности: В 1С 7.7 обращение к ТЗ.Товар.Уровень() внутри цикла обхода большой таблицы может замедлить формирование отчета, так как это вызывает обращение к базе данных. Если строк очень много (десятки тысяч), рекомендуется заранее выгрузить уровни в колонку ТЗ вместе с данными о товаре.
Если ваша конфигурация использует расширение 1С++, задача решается значительно элегантнее с помощью объекта ИндексированнаяТаблица. Этот объект обладает методом Группировать() и ВставитьРодителей(). Эти методы работают на уровне оптимизированных алгоритмов C++ и позволяют мгновенно превратить плоскую таблицу в иерархическую структуру с промежуточными итогами по группам, что является стандартом для качественных отчетов в среде 7.7.