В практике разработки на платформе 1С часто возникает задача получения настроек или параметров, которые могут быть заданы как для конкретного элемента справочника, так и для его родительских групп. Типичный пример — «Ограничение скидки». Если для конкретного товара ограничение не установлено, система должна «подняться» выше по дереву иерархии и найти значение у ближайшего родителя. Сложность возрастает, когда нужно получить такие данные не для одного товара, а для массива из 20–30 тысяч позиций при неограниченной вложенности групп.
Рассмотрим подробнее несколько подходов к решению этой задачи: от простых (но ограниченных) до максимально производительных.
Проанализируем ситуацию, когда глубина вложенности заранее известна или ограничена разумным пределом (например, до 5–10 уровней). В этом случае можно использовать объединение нескольких запросов. Каждый последующий блок ОБЪЕДИНИТЬ ВСЕ будет обращаться к родителям следующего уровня.
Разберем пример кода запроса для одного элемента:
ВЫБРАТЬ ПЕРВЫЕ 1
ОграничениеСкидки КАК Значение
ИЗ
РегистрСведений.ОграниченияСкидок КАК Рег
ГДЕ
Рег.Номенклатура = &Номенклатура
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ПЕРВЫЕ 1
ОграничениеСкидки
ИЗ
РегистрСведений.ОграниченияСкидок КАК Рег
ГДЕ
Рег.Номенклатура = &Номенклатура.Родитель
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ ПЕРВЫЕ 1
ОграничениеСкидки
ИЗ
РегистрСведений.ОграниченияСкидок КАК Рег
ГДЕ
Рег.Номенклатура = &Номенклатура.Родитель.Родитель
Важные моменты этого метода:
Рег.Номенклатура.Родитель.Родитель порождает неявные левые соединения (Left Join) в SQL-запросе. При обработке массива в 30 000 элементов это может привести к значительной деградации производительности и даже падению рабочего процесса rphost.Если мы не знаем глубину иерархии, но хотим получить данные максимально корректно, мы можем использовать цикл в языке 1С, который будет «подниматься» по уровням иерархии до тех пор, пока не найдет значения для всех элементов или не дойдет до корня справочника.
Проанализируем алгоритм:
Пока получаем данные из регистра для тех элементов, которые еще не найдены в «Результатах».Посмотрим на пример логики в коде:
// Шаг 1: Подготовка временной таблицы с исходными данными
Запрос = Новый Запрос("ВЫБРАТЬ Ссылка ПОМЕСТИТЬ ВТ_Остаток ИЗ Справочник.Номенклатура ГДЕ Ссылка В (&Массив)");
Пока Истина Цикл
// Шаг 2: Пытаемся получить значения для текущего уровня
Запрос.Текст =
"ВЫБРАТЬ
| ВТ.Ссылка КАК ИсходнаяНоменклатура,
| Рег.ОграничениеСкидки
|ИЗ
| ВТ_Остаток КАК ВТ
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОграниченияСкидок КАК Рег
| ПО ВТ.Ссылка = Рег.Номенклатура";
Результат = Запрос.Выполнить().Выгрузить();
// (Здесь логика сохранения найденных данных и удаления их из ВТ_Остаток)
// Шаг 3: Переход на уровень выше
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВТ.Ссылка.Родитель КАК Ссылка
|ПОМЕСТИТЬ ВТ_Остаток_Новая
|ИЗ ВТ_Остаток КАК ВТ
|ГДЕ ВТ.Ссылка.Родитель <> ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)";
// Если ВТ_Остаток_Новая пуста - прерываем цикл
КонецЦикла;
Этот метод гораздо стабильнее, так как он не перегружает SQL сложными соединениями, а работает итеративно с индексированными таблицами.
Для задач, где критически важна скорость получения данных (например, расчет скидок в чеке в режиме реального времени), лучшим решением будет создание служебного регистра сведений. Его часто называют «Таблицей связей» или Closure Table.
Выясним причину эффективности этого метода: мы заранее (в фоновом режиме или при записи номенклатуры) просчитываем все связи «Узел — Предок».
Структура служебного регистра «ИерархияНоменклатуры»:
Узел (Элемент или Группа).Предок (Родитель любого уровня или сам Узел).Дистанция (0 — сам элемент, 1 — непосредственный родитель, 2 — «дедушка» и т.д.).Теперь запрос для получения ограничения скидки становится тривиальным и очень быстрым:
ВЫБРАТЬ
Иерархия.Узел КАК Номенклатура,
МИНИМУМ(Иерархия.Дистанция) КАК МинДистанция
ПОМЕСТИТЬ ВТ_Ближайшие
ИЗ
РегистрСведений.ИерархияНоменклатуры КАК Иерархия
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОграниченияСкидок КАК Рег
ПО Иерархия.Предок = Рег.Номенклатура
ГДЕ
Иерархия.Узел В (&МассивНоменклатуры)
СГРУППИРОВАТЬ ПО
Иерархия.Узел;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.Номенклатура,
Рег.ОграничениеСкидки
ИЗ
ВТ_Ближайшие КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ИерархияНоменклатуры КАК Иерархия
ПО ВТ.Номенклатура = Иерархия.Узел И ВТ.МинДистанция = Иерархия.Дистанция
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОграниченияСкидок КАК Рег
ПО Иерархия.Предок = Рег.Номенклатура
Преимущества:
Рассмотрим еще один практический совет. Если настройки меняются редко, а читаются часто, можно пойти по пути избыточности данных. При записи значения в регистр «Ограничение скидки» для группы, мы можем запускать фоновое задание, которое пропишет это же значение всем дочерним элементам в отдельный плоский регистр «АктуальныеОграничения».
Таким образом, при запросе нам не нужно будет знать ничего об иерархии — мы просто соединяемся с регистром по конкретной ссылке Номенклатура. Это самый быстрый способ с точки зрения чтения, но он требует тщательной проработки подписок на события ПриЗаписи справочника и регистра сведений, чтобы поддерживать данные в актуальном состоянии.
Выбор метода зависит от ваших условий:
Родитель.Родитель...), так как это самый верный способ создать очередь на сервере БД.Использование индексации по измерениям регистра и полю Родитель справочника является обязательным условием для любого из выбранных методов.