При интеграции данных и пакетной обработке часто возникает задача поиска элемента справочника по его полному иерархическому коду вида КодГруппы1/КодГруппы2/КодЭлемента. Начинающие разработчики часто пытаются напрямую обратиться к несуществующему полю в запросе (например, Справочник.ПолныйКод), что приводит к синтаксической ошибке СУБД. Разберем особенности хранения иерархии в платформе 1С и рассмотрим все способы решения этой задачи — от штатных платформенных методов до составления оптимизированных запросов.
В физических таблицах базы данных 1С иерархия организуется через ссылки на родителей: в каждой строке хранится только собственный Код и ссылка Родитель (поле ParentIDRRef). Реквизита «Полный код» в базе данных нет — платформа формирует его динамически на клиенте или сервере при обращении к соответствующему свойству объекта или ссылки.
Кроме того, потребность в поиске по всей цепочке кодов возникает преимущественно тогда, когда в конфигураторе для справочника задана настройка «Серии кодов: В пределах подчинения» (или «В пределах подчинения владельцу»). Если установлено свойство «Во всем справочнике», код уникален сам по себе, и поиск родительских цепочек не требуется — достаточно искать просто по коду элемента.
Если поиск выполняется для единичного элемента в программном коде, нет необходимости составлять сложные запросы. В платформе 1С метод менеджера справочника НайтиПоКоду уже содержит встроенный механизм поиска по полному коду.
Синтаксис метода:
Справочники.<ИмяСправочника>.НайтиПоКоду(Код, ПоПолномуКоду, Владелец, На стыке)
Рассмотрим пример использования штатного метода:
// В качестве разделителя уровней иерархии обязательно используется прямой слэш "/"
ПолныйИскомыйКод = "00001/00042/00105";
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоКоду(ПолныйИскомыйКод, Истина);
Если ЗначениеЗаполнено(НайденнаяСсылка) Тогда
// Элемент успешно найден
Сообщить("Найден элемент: " + Строка(НайденнаяСсылка));
Иначе
Сообщить("Элемент с полным кодом " + ПолныйИскомыйКод + " не найден!");
КонецЕсли;
Важные нюансы метода:
/). Если во входных данных используется обратный слэш (\), его необходимо предварительно заменить функцией СтрЗаменить.Если разделители кодов отличаются от стандартных или требуется дополнительная валидация на каждом уровне иерархии, можно разбить строку на массив и выполнить последовательный поиск от корня к конечному элементу:
&НаСервере
Функция НайтиЭлементПоПолномуКоду(Знач ПолныйКод, Знач Разделитель = "\") Экспорт
МассивКодов = СтрРазделить(ПолныйКод, Разделитель, Ложь);
ТекущийРодитель = Справочники.Номенклатура.ПустаяСсылка();
ТекущийЭлемент = ТекущийРодитель;
Для Каждого СтрКод Из МассивКодов Цикл
КодСегмента = СокрЛП(СтрКод);
ТекущийЭлемент = Справочники.Номенклатура.НайтиПоКоду(КодСегмента, Ложь, , ТекущийРодитель);
Если НЕ ЗначениеЗаполнено(ТекущийЭлемент) Тогда
// На одном из уровней элемент или группа не найдены
Возврат Справочники.Номенклатура.ПустаяСсылка();
КонецЕсли;
ТекущийРодитель = ТекущийЭлемент;
КонецЦикла;
Возврат ТекущийЭлемент;
КонецФункции
Когда требуется выполнить поиск через объект Запрос (например, внутри универсальных алгоритмов выгрузки/загрузки), можно динамически сформировать текст запроса с условиями по полям Родитель:
Функция НайтиЭлементЗапросомПоПолномуКоду(ИмяСправочника, ПолныйКод) Экспорт
МассивКодов = СтрРазделить(ПолныйКод, "/", Ложь);
ВсегоСегментов = МассивКодов.Количество();
Если ВсегоСегментов = 0 Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
ТекстУсловий = "";
// Перебираем сегменты справа налево (от самого элемента к верхним родителям)
Для Сч = 0 По ВсегоСегментов - 1 Цикл
ИндексСправа = ВсегоСегментов - 1 - Сч;
ИмяПараметра = "КодУровня_" + Строка(Сч);
ПутьКПолю = "Справочник";
Для Уровень = 1 По Сч Цикл
ПутьКПолю = ПутьКПолю + ".Родитель";
КонецЦикла;
Если Сч > 0 Тогда
ТекстУсловий = ТекстУсловий + " И " + Символы.ПС;
КонецЕсли;
ТекстУсловий = ТекстУсловий + ПутьКПолю + ".Код = &" + ИмяПараметра;
Запрос.УстановитьПараметр(ИмяПараметра, СокрЛП(МассивКодов[ИндексСправа]));
КонецЦикла;
Запрос.Текст =
"ВЫБРАТЬ
| Справочник.Ссылка КАК Ссылка
|ИЗ
| Справочник." + ИмяСправочника + " КАК Справочник
|ГДЕ
| " + ТекстУсловий;
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат Неопределено;
КонецЕсли;
Выборка = РезультатЗапроса.Выбрать();
Выборка.Следующий();
Возврат Выборка.Ссылка;
КонецФункции
Внимание к производительности: разыменование полей через точку (Справочник.Родитель.Родитель...) транслируется СУБД в цепочку внешних соединений (LEFT JOIN). Для справочников с большой глубиной вложенности такой запрос создает дополнительную нагрузку на сервер СУБД.
Если в системе регулярно сопоставляются таблицы значений с тысячами строк по полным кодам, вызов НайтиПоКоду или динамических запросов в цикле приведет к проблеме N+1 запросов и сильному замедлению работы. В таких сценариях рекомендуются следующие подходы:
ПолныйКод (с признаком «Индексировать») и автоматически заполнять его в событии ПередЗаписью модуля объекта. В этом случае поиск по таблице в запросе превращается в быстрое соединение по индексу.ПолныйКод и Ссылка, обновляемый при записи справочника через подписку на событие.КодУровня1, КодУровня2, КодЭлемента), поместить её во временную таблицу и выполнить одно пакетное соединение с таблицей справочника по всем уровням сразу.