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