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