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