Как получить полный код элемента справочника в запросе 1С?

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

В процессе разработки в среде 1С:Предприятие программисты часто сталкиваются с необходимостью получить иерархический путь элемента справочника непосредственно в запросе. На уровне объектной техники (в коде встроенного языка) эта задача решается тривиальным вызовом метода Ссылка.ПолныйКод(). Однако при попытке сделать аналогичное действие в запросе разработчики упираются в серьезное ограничение платформы: язык запросов 1С не поддерживает рекурсивные вызовы и конструкции типа CTE (Common Table Expressions), которые доступны в «чистом» SQL для работы с деревьями произвольной глубины.

В этой статье мы подробно разберем, почему возникает эта проблема, какие существуют варианты ее обхода, и как реализовать наиболее эффективные и производительные алгоритмы получения полного кода элемента справочника на платформе 1С.

Почему нельзя просто вызвать ПолныйКод() в запросе?

Язык запросов 1С транслируется платформой в SQL-запрос, который затем выполняется на стороне СУБД (MS SQL Server, PostgreSQL, IBM DB2 или Oracle). Функция ПолныйКод() является методом объектной модели 1С и требует обращения к серверу приложений для последовательного считывания родителей элемента. В самом SQL-запросе напрямую вызвать этот метод невозможно. СУБД «не знает» о структуре объектов 1С, она оперирует только таблицами и связями между ними.

Давайте проанализируем пять основных способов решения этой задачи в зависимости от архитектурных требований вашей системы.

Решение 1. Конкатенация через конструкцию ВЫБОР (для фиксированной глубины)

Если в вашей конфигурации справочник имеет строго ограниченную и небольшую глубину иерархии (например, не более 3–4 уровней), задачу можно решить «чистым» запросом без динамического формирования текста. Мы можем последовательно проверить заполненность родительских уровней с помощью оператора ВЫБОР.

Рассмотрим пример построения полного кода через разделитель "/" для справочника с максимальной глубиной 3 уровня:


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

Важный нюанс приведения типов (Революция в 1С 8.3.20+): Обратите внимание на тип данных кода вашего справочника. Если код является числовым, то в версиях платформы ниже 8.3.20 сложить его со строковым разделителем "/" напрямую в запросе было невозможно. Приходилось идти на ухищрения. Начиная с релиза 1С 8.3.20, разработчики получили встроенную функцию Строка() (или ВСтроку()) непосредственно в языке запросов. Теперь числовые коды можно безопасно конвертировать прямо во время выполнения запроса на СУБД.

Решение 2. Динамическая сборка текста запроса на встроенном языке

Когда глубина иерархии справочника может меняться или она достаточно велика (например, до 10 уровней), писать статическую конструкцию ВЫБОР вручную становится нецелесообразно. В этом случае мы можем предварительно вычислить максимальную глубину справочника и динамически собрать текст запроса в цикле встроенного языка перед его запуском.

Давайте разберем по шагам, как написать универсальный генератор такого запроса:


// Определяем максимальное количество уровней иерархии справочника.
// В реальной задаче это число можно получить из метаданных или установить с запасом (например, 10).
МаксУровень = Метаданные.Справочники.Номенклатура.КоличествоУровнейИерархии;

ТекстЗапроса = "ВЫБРАТЬ Ссылка, ";
СтрокаВыбора = "ВЫБОР " + Символы.ПС;

Для Уровень = 1 По МаксУровень Цикл
	Если Уровень = 1 Тогда
		// Для первого уровня проверка на отсутствие родителя
		Условие = "Родитель ЕСТЬ NULL";
		ВыражениеКода = "Код";
	Иначе
		// Строим цепочку родителей для проверки на NULL
		ПутьКРодителю = "Родитель";
		Для Шаг = 3 По Уровень Цикл
			ПутьКРодителю = ПутьКРодителю + ".Родитель";
		КонецЦикла;
		
		Условие = ПутьКРодителю + ".Родитель ЕСТЬ NULL";
		
		// Собираем конкатенацию кодов от самого верхнего предка до текущего элемента
		ВыражениеКода = "";
		Для Шаг = Уровень - 1 НазадПо 1 Цикл
			ТекущийРодитель = "Родитель";
			Для Итерация = 2 По Шаг Цикл
				ТекущийРодитель = ТекущийРодитель + ".Родитель";
			КонецЦикла;
			ВыражениеКода = ВыражениеКода + ТекущийРодитель + ".Код + ""/"" + ";
		КонецЦикла;
		ВыражениеКода = ВыражениеКода + "Код";
	КонецЕсли;
	
	СтрокаВыбора = СтрокаВыбора + "    КОГДА " + Условие + " ТОГДА " + ВыражениеКода + Символы.ПС;
КонецЦикла;

СтрокаВыбора = СтрокаВыбора + "КОНЕЦ КАК ПолныйКод ";
ТекстЗапроса = ТекстЗапроса + СтрокаВыбора + " ИЗ Справочник.Номенклатура";

// Теперь передаем собранный ТекстЗапроса в объект Запрос и выполняем его
Запрос = Новый Запрос(ТекстЗапроса);
Результат = Запрос.Выполнить().Выгрузить();

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

Решение 3. Обход через вычисляемые поля Системы компоновки данных (СКД)

Если итоговой целью получения полного кода является вывод информации в отчет, то нам абсолютно не обязательно мучить СУБД тяжелыми запросами со множественными соединениями таблиц. Мы можем использовать мощный механизм вычисляемых полей СКД совместно с функциями общих модулей конфигурации.

Рассмотрим пошаговый алгоритм настройки этого механизма:

  1. Создание общего модуля: Создайте (или используйте существующий) общий модуль, например, с именем ОбщегоНазначенияКлиентСервер. Убедитесь, что у модуля установлены флаги Сервер, Клиент (управляемое приложение) и Вызов сервера.
  2. Написание функции: Добавьте в модуль экспортную функцию получения полного кода:
    
    Функция ПолучитьПолныйКодЭлемента(СсылкаНаЭлемент) Экспорт
    	Возврат СсылкаНаЭлемент.ПолныйКод();
    КонецФункции
    
  3. Настройка вычисляемого поля в СКД:
    • Откройте схему компоновки данных вашего отчета.
    • Перейдите на вкладку Вычисляемые поля.
    • Добавьте новую строку. Задайте имя поля, например, ПолныйКод.
    • В колонке Выражение пропишите вызов нашей функции: ОбщегоНазначенияКлиентСервер.ПолучитьПолныйКодЭлемента(Ссылка).

При формировании отчета СКД сама порционно передаст ссылки на элементы справочника в общую функцию и заполнит результирующее поле. Это изящный способ, который позволяет сохранить чистоту исходного SQL-запроса и переложить задачу склеивания путей на плечи сервера приложений 1С.

Решение 4. Пошаговый обход дерева (псевдорекурсия) во временных таблицах

Для оптимизации обработки больших объемов данных без перегрузки памяти сервера приложений можно использовать пошаговый алгоритм с использованием временных таблиц. Суть метода заключается в том, что мы выполняем соединение таблицы родителей с таблицей детей итерационно во временных таблицах до тех пор, пока очередной запрос не вернет пустой результат (то есть мы достигнем самого нижнего листа дерева).

Этот подход требует выполнения цикла во встроенном языке, но внутри цикла крутится не построчная обработка, а один массовый пакетный запрос к СУБД. Это позволяет обработать тысячи элементов за сотые доли секунды.

Решение 5. Архитектурно правильный подход: Денормализация данных (Кэширование)

В высоконагруженных корпоративных системах (highload) сборка полного кода «на лету» в запросах или в цикле признается грубой архитектурной ошибкой. Подобные операции приводят к колоссальным нагрузкам на дисковую подсистему СУБД из-за необходимости постоянного сканирования таблиц и выполнения тяжелых операций LEFT JOIN.

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

Давайте разберем, как правильно реализовать этот подход:

  1. Добавление реквизита: Добавьте в ваш справочник (например, Номенклатура) новый реквизит ПолныйКодДляПоиска. Задайте ему тип Строка достаточной длины (например, 150-250 символов) и обязательно установите свойство Индексировать в значение Индексировать (или Индексировать с доп. упорядочиванием).
  2. Написание обработчика записи: В модуле объекта справочника пропишите логику автоматического заполнения этого реквизита при любом сохранении элемента.
    
    Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    	Если ОбменДанными.Загрузка Тогда
    		Возврат;
    	КонецЕсли;
    
    	// Принудительно вычисляем полный код
    	ЭтотОбъект.ПолныйКодДляПоиска = ЭтотОбъект.ПолныйКод();
    КонецПроцедуры
    
  3. Обработка изменения родителя у подчиненных элементов: Важно понимать, что если у родительской группы меняется код или наименование, у всех ее потомков автоматически должен обновиться сохраненный полный код. Для этого в подписке на событие ПриЗаписи или прямо в модуле объекта необходимо реализовать рекурсивный обход подчиненных элементов и их перезапись при смене родителя.

Благодаря денормализации данных, любой ваш запрос по выборке полного кода будет выполняться за микросекунды. Вам больше не придется использовать соединения, конструкции ВЫБОР или функции общих модулей. Запрос станет максимально простым и производительным:


ВЫБРАТЬ
	Номенклатура.Ссылка КАК Ссылка,
	Номенклатура.ПолныйКодДляПоиска КАК ПолныйКод
ИЗ
	Справочник.Номенклатура КАК Номенклатура
ГДЕ
	Номенклатура.ПолныйКодДляПоиска ПОДОБНО "00001/00025/%"

Использование индексированного поля ПолныйКодДляПоиска также открывает возможность мгновенного поиска по иерархическому дереву с использованием оператора ПОДОБНО.

Резюме

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

← На главную