Как вывести всех родителей номенклатуры в одну ячейку в 1С:Предприятие 8

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

При построении отчетов, реестров товаров или прайс-листов часто возникает задача вывести полную цепочку иерархии (всех родительских групп) для каждой позиции номенклатуры в одну ячейку. Например, мы хотим получить строку вида: "Для дома / Мебель / Кухонная мебель" для элемента "Стул".

В этой статье мы подробно разберем, как решить эту задачу различными методами: от простого запроса с жестким ограничением вложенности до программных алгоритмов и встроенных механизмов Системы компоновки данных (СКД). Мы проанализируем частые ошибки разработчиков и выберем оптимальные подходы.

---

Метод 1. Сборка цепочки в запросе через выборку (для фиксированной вложенности)

Этот способ идеально подходит, если глубина иерархии в вашей базе данных ограничена и заранее известна (например, не превышает 4-5 уровней). Мы можем написать один запрос, который на уровне СУБД склеит наименования родительских групп.

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


// ОШИБОЧНЫЙ КОД! Сравнение ссылки со строкой
ВЫБОР 
    КОГДА СпрНоменклатура.Родитель = "" 
        ТОГДА СпрНоменклатура.Родитель.Родитель 
    ...

Выясним причину ошибки: в базе данных 1С ссылочные поля (такие как Родитель или Ссылка) хранятся в виде уникальных идентификаторов (UUID). Попытка сравнить ссылку с пустой строкой "" на уровне базы данных всегда будет возвращать Ложь (или вызовет ошибку несовместимости типов). Из-за этого запрос всегда будет уходить в ветку ИНАЧЕ, возвращая некорректный или пустой результат.

Чтобы исправить это, мы должны использовать специальную конструкцию ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка). Ниже представлен правильный и оптимизированный запрос, который выводит цепочку родителей (до 4 уровней) от верхнего к нижнему:


ВЫБРАТЬ
    СпрНоменклатура.Ссылка КАК Товар,
    ВЫБОР
        // Если у номенклатуры вообще нет родителя (лежит в корне)
        КОГДА СпрНоменклатура.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) 
            ТОГДА ""
            
        // Если есть только 1 уровень родителя
        КОГДА СпрНоменклатура.Родитель.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) 
            ТОГДА СпрНоменклатура.Родитель.Наименование
            
        // Если есть 2 уровня родителей
        КОГДА СпрНоменклатура.Родитель.Родитель.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) 
            ТОГДА СпрНоменклатура.Родитель.Родитель.Наименование + " / " + СпрНоменклатура.Родитель.Наименование
            
        // Если есть 3 уровня родителей
        КОГДА СпрНоменклатура.Родитель.Родитель.Родитель.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка) 
            ТОГДА СпрНоменклатура.Родитель.Родитель.Родитель.Наименование + " / " + СпрНоменклатура.Родитель.Родитель.Наименование + " / " + СпрНоменклатура.Родитель.Наименование
            
        // Если уровней 4 и более
        ИНАЧЕ СпрНоменклатура.Родитель.Родитель.Родитель.Родитель.Наименование + " / " + СпрНоменклатура.Родитель.Родитель.Родитель.Наименование + " / " + СпрНоменклатура.Родитель.Родитель.Наименование + " / " + СпрНоменклатура.Родитель.Наименование
    КОНЕЦ КАК ПутьКНоменклатуре
ИЗ
    Справочник.Номенклатура КАК СпрНоменклатура
ГДЕ
    НЕ СпрНоменклатура.ЭтоГруппа

Преимущества метода: выполняется чрезвычайно быстро на стороне СУБД за один проход, не требует написания серверного кода на встроенном языке.

Недостатки: если пользователь создаст группу 6-го или 7-го уровня вложенности, данные о самых верхних родителях начнут "обрезаться" или отображаться некорректно.

---

Метод 2. Программный обход на встроенном языке 1С (Рекомендация фирмы "1С")

Если глубина дерева папок неизвестна или может быть очень большой, использовать конструкции ВЫБОР КОГДА в запросе становится нецелесообразно. В этом случае мы должны обрабатывать результат программно.

Частая ошибка программистов при решении этой задачи "в лоб" — обращение через точку в цикле:


// ТАК ДЕЛАТЬ НЕ РЕКОМЕНДУЕТСЯ! (Проблема N+1 запросов)
Пока ЗначениеЗаполнено(ТекущийРодитель) Цикл
    СтрокаПути = СтрокаПути + " / " + ТекущийРодитель.Наименование;
    ТекущийРодитель = ТекущийРодитель.Родитель; // Каждый проход цикла делает отдельный запрос к базе!
КонецЦикла;

Для предотвращения перегрузки СУБД фирма "1С" рекомендует запрашивать данные пакетами по несколько уровней иерархии за один раз. Разберем эффективный алгоритм, который получает до 5 уровней родителей за одну итерацию запроса:


Функция ПолучитьПолныйПутьРодителей(СсылкаНаТовар) Экспорт
    
    СписокРодителей = Новый СписокЗначений;
    ТекущийЭлемент = СсылкаНаТовар;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.Родитель КАК Р1,
        |	Номенклатура.Родитель.Родитель КАК Р2,
        |	Номенклатура.Родитель.Родитель.Родитель КАК Р3,
        |	Номенклатура.Родитель.Родитель.Родитель.Родитель КАК Р4,
        |	Номенклатура.Родитель.Родитель.Родитель.Родитель.Родитель КАК Р5
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |	Номенклатура.Ссылка = &ТекущийЭлемент";
        
    Пока Истина Цикл
        Запрос.УстановитьПараметр("ТекущийЭлемент", ТекущийЭлемент);
        Выгрузка = Запрос.Выполнить().Выгрузить();
        
        Если Выгрузка.Количество() = 0 Тогда
            Прервать;
        КонецЕсли;
        
        СтрокаДанных = Выгрузка[0];
        Для НомерКолонки = 0 По Выгрузка.Колонки.Количество() - 1 Цикл
            РодительУровня = СтрокаДанных[НомерКолонки];
            Если НЕ ЗначениеЗаполнено(РодительУровня) Тогда
                Прервать;
            КонецЕсли;
            // Добавляем в начало списка, чтобы соблюсти порядок от корня к элементу
            СписокРодителей.Вставить(0, РодительУровня.Наименование);
            ТекущийЭлемент = РодительУровня;
        КонецЦикла;
        
        // Если мы не заполнили последнюю колонку (Р5), значит дошли до корня
        Если НЕ ЗначениеЗаполнено(ТекущийЭлемент) Или Выгрузка.Колонки.Количество() < 5 Тогда
            Прервать;
        КонецЕсли;
    КонецЦикла;
    
    // Собираем массив наименований в единую строку с разделителем
    МассивРодителей = СписокРодителей.ВыгрузитьЗначения();
    Возврат СтрСоединить(МассивРодителей, " / ");
    
КонецФункции

Этот метод гарантирует минимальное количество обращений к СУБД даже при экстремальной глубине вложенности справочника.

---

Метод 3. Использование платформенного метода ПолныйКод()

Существует интересный и быстрый "лайфхак" для конфигураций, в которых коды номенклатуры уникальны в пределах подчинения (иерархический код).

Платформенный метод Ссылка.ПолныйКод() возвращает строку, состоящую из кодов всех родителей, разделенных косой чертой, например: "00-000001/00-000014/00-000251". Мы можем использовать этот метод, чтобы быстро получить список родителей за один легкий запрос к базе данных.

Рассмотрим, как реализовать этот алгоритм:

  1. Получаем строку полного кода для элемента: ПолныйКодЭлемента = СсылкаНаТовар.ПолныйКод();.
  2. Разбиваем строку в массив по разделителю "/".
  3. Передаем полученный массив кодов в один простой запрос для получения наименований этих групп.
  4. Соединяем полученные наименования обратно в строку.

Посмотрим на пример реализации данного метода:


Функция ПолучитьПутьЧерезПолныйКод(СсылкаНаТовар) Экспорт
    
    ПолныйКодСтрока = СсылкаНаТовар.ПолныйКод();
    // Исключаем код самого элемента (последний сегмент)
    МассивКодов = СтрРазделить(ПолныйКодСтрока, "/");
    Если МассивКодов.Количество() <= 1 Тогда
        Возврат ""; // Родителей нет, элемент лежит в корне
    КонецЕсли;
    
    // Удаляем последний элемент (это код самого товара, а не родителя)
    МассивКодов.Удалить(МассивКодов.Количество() - 1);
    
    // Запросом получаем наименования для этих кодов, чтобы сохранить правильный порядок
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Номенклатура.Наименование КАК Наименование,
        |	Номенклатура.Код КАК Код
        |ИЗ
        |	Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |	Номенклатура.Код В (&МассивКодов)
        |	И Номенклатура.ЭтоГруппа";
        
    Запрос.УстановитьПараметр("МассивКодов", МассивКодов);
    ТЗРезультат = Запрос.Выполнить().Выгрузить();
    
    // Сопоставляем наименования с исходным порядком кодов
    МассивИмен = Новый Массив;
    Для Каждого ТекущийКод Из МассивКодов Цикл
        СтрокаПоиска = ТЗРезультат.Найти(ТекущийКод, "Код");
        Если СтрокаПоиска <> Неопределено Тогда
            МассивИмен.Добавить(СтрокаПоиска.Наименование);
        КонецЕсли;
    КонецЦикла;
    
    Возврат СтрСоединить(МассивИмен, " / ");
    
КонецФункции

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

---

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

Если вы выводите данные в отчет с помощью СКД, вам вообще не нужно писать сложный программный код или громоздкие запросы с ВЫБОР КОГДА. Платформа 1С умеет автоматически оперировать иерархическими связями.

Проанализируем два изящных способа настройки СКД:

Вариант А. Связи наборов данных (рекурсивный запрос)

В схеме компоновки данных можно настроить связь набора данных "сам с собой":

  1. Создаем основной набор данных (запрос), который выбирает номенклатуру.
  2. Создаем второй набор данных (приемник), который по параметру &Родитель выбирает информацию о родительской группе.
  3. На вкладке "Связи наборов данных" связываем первый набор со вторым. В качестве источника связи указываем поле Родитель, в качестве приемника — Ссылка.
  4. Указываем условие связи и включаем параметр связи. СКД сама рекурсивно выполнит запросы по ветке иерархии вверх до самого корня.

Вариант Б. Функция "ВычислитьВыражение" в вычисляемых полях

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


// Пример выражения для вычисляемого поля в СКД
ВычислитьВыражение("Представление(Родитель)", , "Иерархия", "Первая", "Текущая")

Это выражение автоматически соберет коллекцию представлений всех вышестоящих родителей для текущей строки отчета и объединит их. Нам останется лишь настроить оформление поля.

---

Резюме: какой метод выбрать?

← На главную