Родитель, вся цепочка групп и корень иерархии в 1С 8.3: объектная модель, язык запросов, план счетов и ПВХ

· 9 мин

Содержание

Родителя в 1С получают через стандартный реквизит Родитель: в коде это Ссылка.Родитель, в запросе — поле Таблица.Родитель. Частая ошибка — ждать, что Родитель сразу вернёт весь путь «Товары / Бытовая техника / Холодильники». Он возвращает только ближайшую группу. Всех родителей до корня нужно собрать циклом или запросом с несколькими уровнями.

Механизм общий для платформы 8.3 и не зависит от конфигурации. Одинаково работает в БП 3.0, ЗУП 3.1, УТ 11 и самописных базах: в справочниках, планах счетов и иерархических планах видов характеристик (ПВХ). Ниже — рабочий код для каждого случая и то, что ломается на больших данных.

Как устроена иерархия и где хранится родитель

Родитель есть только у объектов, которые поддерживают иерархию. В конфигураторе это флажок Иерархический на вкладке «Иерархия» свойств справочника или ПВХ. У плана счетов иерархия встроена: субсчёт всегда подчинён счёту.

У справочника бывает два вида иерархии:

Вид иерархииКто может быть родителемПризнак ЭтоГруппа
Иерархия групп и элементовтолько группа (папка)у групп Истина, у элементов Ложь
Иерархия элементовлюбой элементвсегда Ложь

📌 Что держать в голове:

  • 🔹 Родитель — ссылка того же типа, что и сам объект. У Справочник.Номенклатура родитель тоже СправочникСсылка.Номенклатура.
  • 🔹 У элемента верхнего уровня Родитель — пустая ссылка, а не Неопределено.
  • 🔹 Флажок «Ограничение количества уровней» и число уровней в свойствах справочника задают максимальную глубину. Если ограничение снято, глубина не ограничена.
  • 🔹 Владелец и Родитель — разные вещи. Владелец — это объект из другого справочника, которому подчинён элемент (договор подчинён контрагенту). Родитель — группа внутри того же справочника.

Родитель в коде модуля

На сервере всё просто:

&НаСервере
Процедура ПоказатьРодителяНаСервере()
	Родитель = Объект.Ссылка.Родитель;
	Если ЗначениеЗаполнено(Родитель) Тогда
		Сообщить("Группа: " + Строка(Родитель));
	Иначе
		Сообщить("Элемент лежит в корне справочника");
	КонецЕсли;
КонецПроцедуры

В форме элемента родитель уже есть в данных формы: Объект.Родитель. Обращаться к базе не нужно.

На тонком клиенте нельзя получить реквизит ссылки через точку. Код Ссылка.Родитель в процедуре &НаКлиенте выдаст ошибку. Вынесите получение в серверную функцию или общий модуль с флажком «Сервер». В конфигурациях на БСП есть готовая функция ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Родитель").

Проверка «входит ли элемент в группу на любой глубине» делается без цикла, методом ПринадлежитЭлементу:

Если Товар.ПринадлежитЭлементу(ГруппаБытоваяТехника) Тогда
	// товар лежит в этой группе или в любой её подгруппе
КонецЕсли;

Родитель в запросе: поле, разыменование и В ИЕРАРХИИ

В языке запросов родитель — обычное поле таблицы. Его можно выбрать, отфильтровать по нему и разыменовать через точку.

ВЫБРАТЬ
	Номенклатура.Ссылка КАК Товар,
	Номенклатура.Родитель КАК Группа,
	Номенклатура.Родитель.Наименование КАК ИмяГруппы
ИЗ
	Справочник.Номенклатура КАК Номенклатура
ГДЕ
	НЕ Номенклатура.ЭтоГруппа

Три приёма, которые нужны почти в каждой задаче с иерархией:

  1. Элементы верхнего уровня. Отбор по пустому родителю: ГДЕ Номенклатура.Родитель = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка).
  2. Прямые потомки группы — только первый уровень вложенности: ГДЕ Номенклатура.Родитель = &Группа.
  3. Вся ветка на любой глубине — оператор В ИЕРАРХИИ: ГДЕ Номенклатура.Ссылка В ИЕРАРХИИ (&Группа).

💡 В ИЕРАРХИИ включает в результат и саму группу из параметра. Если она не нужна, добавьте И Номенклатура.Ссылка <> &Группа или И НЕ Номенклатура.ЭтоГруппа.

Тот же оператор работает с измерениями регистров. Остатки по всем субсчетам 60-го счёта берут так: ГДЕ Счет В ИЕРАРХИИ (&Счет60). Параметр может быть и списком значений: В ИЕРАРХИИ (&СписокГрупп).

Вся цепочка родителей до корня программно

Задача «получить всех родителей» — это подъём от элемента вверх, пока родитель не станет пустым. Функция ниже возвращает массив групп от корня к ближайшей. Её удобно держать в серверном общем модуле.

// Возвращает массив родителей: [0] — корневая группа, последний — ближайший родитель.
Функция ЦепочкаРодителей(Ссылка, МаксГлубина = 100) Экспорт

	Результат = Новый Массив;
	Пройденные = Новый Соответствие;

	Текущий = Ссылка.Родитель;
	Пока ЗначениеЗаполнено(Текущий) Цикл

		Если Пройденные[Текущий] <> Неопределено
			Или Результат.Количество() >= МаксГлубина Тогда
			ВызватьИсключение "Ошибка иерархии: зацикливание у " + Строка(Ссылка);
		КонецЕсли;

		Пройденные.Вставить(Текущий, Истина);
		Результат.Вставить(0, Текущий);
		Текущий = Текущий.Родитель;

	КонецЦикла;

	Возврат Результат;

КонецФункции

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

Функция ПолныйПуть(Ссылка, Разделитель = " / ") Экспорт

	Части = Новый Массив;
	Для Каждого Группа Из ЦепочкаРодителей(Ссылка) Цикл
		Части.Добавить(Строка(Группа));
	КонецЦикла;
	Части.Добавить(Строка(Ссылка));

	Возврат СтрСоединить(Части, Разделитель);

КонецФункции

Для холодильника получится, например, Товары / Бытовая техника / Холодильники / Холодильник Х-200. Метод СтрСоединить есть с платформы 8.3.6.

Корень и уровень элемента

  • 🔝 Верхний родитель (корень) — первый элемент массива: Цепочка[0], если массив не пустой. Пустой массив значит, что элемент сам лежит в корне.
  • 📏 Уровень вложенности — Цепочка.Количество(). У элемента в корне уровень 0. У объекта справочника и у выборки есть метод Уровень() с тем же смыслом.
  • 🔁 Нужен ли «Пройденные» в Соответствии? Платформа не даёт перенести группу в её же подгруппу при интерактивной записи. Но при загрузке через обмен с ОбменДанными.Загрузка = Истина проверки отключены, и кольцо из двух групп возможно. Без защиты цикл станет вечным и повесит сеанс.

Группа заданного уровня

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

// Уровень считается от корня: 1 — верхняя группа, 2 — её подгруппа и так далее.
Функция ГруппаУровня(Ссылка, НомерУровня) Экспорт
	Цепочка = ЦепочкаРодителей(Ссылка);
	Если Цепочка.Количество() >= НомерУровня Тогда
		Возврат Цепочка[НомерУровня - 1];
	КонецЕсли;
	Возврат Неопределено;
КонецФункции

Если товар лежит выше запрошенного уровня, функция вернёт Неопределено. Решите заранее, что показывать в отчёте в этом случае: пустую строку или ближайшую группу.

Все родители одним запросом

Рекурсивных запросов в языке 1С нет. Подняться по дереву одним запросом можно двумя способами.

Способ 1. Фиксированная глубина через разыменование

Если глубина справочника известна (например, ограничена четырьмя уровнями), выбирайте родителей через цепочку точек:

ВЫБРАТЬ
	Т.Ссылка КАК Ссылка,
	Т.Родитель КАК Р1,
	Т.Родитель.Родитель КАК Р2,
	Т.Родитель.Родитель.Родитель КАК Р3,
	Т.Родитель.Родитель.Родитель.Родитель КАК Р4
ИЗ
	Справочник.Номенклатура КАК Т
ГДЕ
	Т.Ссылка В (&СписокТоваров)

Каждая точка — это соединение таблицы самой с собой. Четыре уровня — четыре соединения, и это быстро. Если глубина не ограничена, проверьте поле Р4: заполнено — запустите тот же запрос ещё раз, передав в параметр значения Р4. Так обрабатывается сколько угодно элементов за несколько обращений к базе, а не по запросу на каждый уровень каждого товара.

Способ 2. ИТОГИ ПО … ИЕРАРХИЯ

Ключевое слово ИЕРАРХИЯ в итогах заставляет платформу добавить в результат строки всех вышестоящих групп:

Запрос = Новый Запрос;
Запрос.Текст =
	"ВЫБРАТЬ
	|	Т.Ссылка КАК Номенклатура
	|ИЗ
	|	Справочник.Номенклатура КАК Т
	|ГДЕ
	|	Т.Ссылка = &Товар
	|ИТОГИ ПО
	|	Номенклатура ИЕРАРХИЯ";
Запрос.УстановитьПараметр("Товар", Товар);

Выборка = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.Прямой);
Пока Выборка.Следующий() Цикл
	Если Выборка.ТипЗаписи() = ТипЗаписиЗапроса.ИтогПоИерархии Тогда
		Сообщить("Группа: " + Строка(Выборка.Номенклатура));
	Иначе
		Сообщить("Элемент: " + Строка(Выборка.Номенклатура));
	КонецЕсли;
КонецЦикла;

Строки групп идут сверху вниз, от корня к ближайшему родителю. Способ удобен для отчётов и выгрузок, где нужно дерево с подытогами. Для одного элемента он избыточен: проще функция из предыдущего раздела.

План счетов, ПВХ и другие иерархические объекты

Принцип везде одинаковый, различаются детали:

ОбъектЕсть ли РодительОсобенности
Справочникесли включена иерархиябывают группы (ЭтоГруппа) или иерархия элементов
План счетоввсегдагрупп нет, родитель субсчёта — счёт: у 60.01 родитель 60
План видов характеристикесли включена иерархияесть группы и ЭтоГруппа, как у справочника
Документ, регистрнетиерархия только через реквизиты или измерения ссылочного типа

План счетов

Родитель субсчёта второго уровня получают так же: Счет.Родитель. В запросе к плану счетов Хозрасчетный (БП 3.0) работает и В ИЕРАРХИИ, и разыменование:

ВЫБРАТЬ
	Х.Ссылка КАК Счет,
	Х.Код КАК Код,
	Х.Родитель.Код КАК КодРодителя
ИЗ
	ПланСчетов.Хозрасчетный КАК Х
ГДЕ
	Х.Ссылка В ИЕРАРХИИ (ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.РасчетыСПоставщиками))

💡 В регистре бухгалтерии отбор по счёту почти всегда пишут через В ИЕРАРХИИ. Проводки ложатся на субсчета, и отбор Счет = &Счет60 по самому счёту 60 вернёт пустоту.

ПВХ

Иерархический план видов характеристик ведёт себя как справочник с группами. Типичный пример — дополнительные реквизиты и свойства, сгруппированные по наборам. Поля Родитель и ЭтоГруппа доступны в коде и в запросе. Функция ЦепочкаРодителей работает без изменений: тип ссылки она не проверяет.

Подчинённые справочники

Если справочник подчинён другому, у элемента есть и Владелец, и (при иерархии) Родитель. Путь «контрагент → группа договоров → договор» собирают в два шага: сначала цепочка родителей, затем Владелец любого элемента цепочки. Владелец у всех элементов одной ветки совпадает.

Пустой родитель, битые ссылки и зацикливание

Иерархия в рабочей базе редко бывает идеальной. Три ситуации, которые обязательно встретятся.

Диагностика: выберите симптом

Запрос для поиска элементов с битым родителем:

ВЫБРАТЬ
	Т.Ссылка КАК Элемент,
	Т.Родитель КАК Родитель
ИЗ
	Справочник.Номенклатура КАК Т
		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Р
		ПО Т.Родитель = Р.Ссылка
ГДЕ
	Т.Родитель <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
	И Р.Ссылка ЕСТЬ NULL

Перед массовым переносом элементов между группами сделайте копию базы — изменение Родитель у тысяч элементов отменить одной кнопкой нельзя.

Скорость: что тормозит на больших справочниках

Сама иерархия не медленная: по полю Родитель у иерархического справочника платформа держит индекс. Тормозит то, как к ней обращаются.

СпособОбращений к базеКогда подходит
Ссылка.Родитель в цикле по одному элементупо одному на уровеньединичный элемент, форма, проверка
ЦепочкаРодителей для 10 000 товаровдесятки тысячне подходит, переписать на запрос
Запрос с Р1…Р4 порциями1–3 запроса на весь списоквыгрузки, обработки, отчёты
ИТОГИ … ИЕРАРХИЯодин запросотчёты с деревом и подытогами
В ИЕРАРХИИодин запросотбор всей ветки по группе

Что ещё помогает:

  • ⚡ Не разыменовывайте в цикле по выборке. Выборка.Товар.Родитель.Наименование в цикле — это отдельное чтение объекта на каждой строке. Добавьте поле Т.Родитель.Наименование прямо в текст запроса.
  • ⚡ Кешируйте результат. Если путь группы нужен много раз за один вызов, храните его в Соответствии с ключом-ссылкой группы. У тысячи товаров обычно несколько десятков групп.
  • ⚡ Ограничивайте глубину разыменования. Цепочка из 8–10 точек Родитель.Родитель… даёт столько же соединений и тяжёлый план запроса. Лучше 3–4 уровня и повтор порцией.

Частые вопросы

Можно ли вручную поменять родителя элемента из кода?
Да. Получите объект Ссылка.ПолучитьОбъект(), присвойте Объект.Родитель = НоваяГруппа и вызовите Объект.Записать(). При иерархии групп и элементов родителем может быть только группа этого же справочника.
Как получить всех потомков группы, а не родителей?
Запросом с условием Ссылка В ИЕРАРХИИ (&Группа). Только прямых потомков — условием Родитель = &Группа.
Как получить родителя у элемента табличной части документа?
Через ссылку в строке: Строка.Номенклатура.Родитель на сервере или поле Товары.Номенклатура.Родитель в запросе к табличной части Документ.Имя.Товары. У самой строки табличной части родителя нет.
Как вывести иерархию в отчёте на СКД?
В настройках группировки выберите тип «Иерархия» для поля с иерархическим типом. Подытоги по группам СКД посчитает сама, без ИТОГИ в тексте запроса.
Чем родитель отличается от владельца в запросе?
Родитель — группа в том же справочнике, Владелец — элемент другого справочника. Для отбора договоров одного контрагента пишут Владелец = &Контрагент, а не В ИЕРАРХИИ.