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