Как получить значение от группы для всех вложенных элементов номенклатуры в запросе 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Часто в практике разработки на платформе 1С возникает классическая задача: имеется регистр сведений, где хранятся определенные показатели (например, скидки, наценки или проценты). Эти значения могут быть заданы как для конкретного элемента справочника (например, номенклатуры), так и для группы в целом. Если значение задано для элемента, нужно использовать его. Если для элемента значение не задано, но оно есть у родительской группы — необходимо унаследовать это значение для всех подчиненных элементов.

Рассмотрим подробнее, как оптимально решить эту задачу, особенно если итоговый результат планируется выводить с использованием Системы Компоновки Данных (СКД). Разберем несколько подходов: от архитектурной переделки логики хранения до написания запросов с учетом особенностей иерархии.

Решение 1: Архитектурный подход (Денормализация при записи)

Проанализируем ситуацию с точки зрения нагрузки на сервер БД. Использование сложных рекурсивных запросов для получения иерархии "на лету" при формировании отчета сильно бьет по производительности. Оптимальнее всего перенести вычислительную нагрузку с момента чтения данных на момент их записи.

Разберем по шагам, как внедрить такой механизм:

  1. В регистр сведений добавьте служебный реквизит НаследованноеЗначение типа Булево. Это поможет отличать явно заданные значения от тех, что сгенерированы автоматически.
  2. Вместо прямого редактирования регистра в списке, реализуйте запись через отдельную форму, АРМ или Подписку на события (обработчик ПриЗаписи регистра).
  3. При установке процента для группы номенклатуры, программно получите все нижестоящие элементы (здесь отлично отработает условие В ИЕРАРХИИ).
  4. Создайте и запишите наборы записей регистра для каждого найденного вложенного элемента с указанным процентом и флагом НаследованноеЗначение = Истина.
  5. Если для подчиненного элемента уже есть свое явно заданное значение (флаг сброшен) — пропускаем его.
  6. Не забудьте обрабатывать удаление: если значение у группы удаляется, нужно очистить все унаследованные значения по этой ветке.

При таком подходе запрос для СКД или любого другого отчета становится элементарным — мы просто обращаемся к регистру по ссылке на саму номенклатуру, без соединений с родителями. Отчет будет работать максимально шустро.

Решение 2: Прагматичный подход (Множественные левые соединения)

Если изменять архитектуру конфигурации недопустимо, рассмотрим решение на уровне самого запроса. В реальных базах данных глубина иерархии справочника Номенклатура редко превышает 5–7 уровней. Мы можем использовать фиксированное количество левых соединений таблицы справочника с регистром сведений.

Посмотрим на пример логики связей:

  1. Первое левое соединение делаем по самой ссылке элемента.
  2. Второе — по полю Родитель текущего элемента.
  3. Третье — по полю Родитель.Родитель.
  4. И так далее, до разумного ограничения уровней вложенности.

В секции выбора полей мы применяем функцию ЕСТЬNULL (или конструкцию ВЫБОР КОГДА), чтобы по цепочке проверить наличие заданного процента, начиная с самого элемента и поднимаясь вверх по иерархии.


ВЫБРАТЬ
    Номенклатура.Ссылка КАК Ссылка,
    ЕСТЬNULL(РегСсылка.Процент, 
        ЕСТЬNULL(РегРодитель1.Процент, 
            ЕСТЬNULL(РегРодитель2.Процент, 
                ЕСТЬNULL(РегРодитель3.Процент, 0)))) КАК Процент
ИЗ
    Справочник.Номенклатура КАК Номенклатура
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Проценты КАК РегСсылка
        ПО Номенклатура.Ссылка = РегСсылка.Номенклатура
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Проценты КАК РегРодитель1
        ПО Номенклатура.Родитель = РегРодитель1.Номенклатура
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Проценты КАК РегРодитель2
        ПО Номенклатура.Родитель.Родитель = РегРодитель2.Номенклатура
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Проценты КАК РегРодитель3
        ПО Номенклатура.Родитель.Родитель.Родитель = РегРодитель3.Номенклатура
ГДЕ
    НЕ Номенклатура.ЭтоГруппа

Важное ограничение: Конструкция языка запросов В ИЕРАРХИИ работает только в секции условий или в параметрах виртуальных таблиц. Ее невозможно применить в условии левого соединения, поэтому такой прямолинейный подход со связями по уровням является одним из самых надежных и быстрых.

Решение 3: Использование связей наборов данных в СКД

Поскольку задача решается в контексте отчета на компоновке данных, рассмотрим элегантный способ средствами самой СКД без монструозных запросов.

  1. Создаем первый набор данных — запрос, который получает список номенклатуры и ее иерархию (ссылка и родитель).
  2. Создаем второй набор данных — запрос, получающий все записи из регистра сведений.
  3. Переходим на вкладку "Связи наборов данных". Связываем первый набор со вторым по полю Номенклатура.
  4. В вычисляемых полях СКД используем функцию ВычислитьВыражение(). Мы можем настроить выражение так, чтобы при пустом значении у элемента (NULL) бралось значение из группировки родителя.

Решение 4: Программная сборка временных таблиц в цикле

Если глубина иерархии заранее неизвестна, и мы не используем СКД (или готовы переопределять запрос в обработчике ПриКомпоновкеРезультата), применим метод итерационного обхода с использованием временных таблиц.

Разберем логику построения такого алгоритма:


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

Внутри каждого объединения мы сразу проверяем наличие цены/процента: если для номенклатуры текущего уровня значение задано, берем его, в противном случае берем значение, переданное от номенклатуры уровня N-1.

Резюме: Если вам нужна максимальная скорость отчетов, внедряйте денормализацию (Решение 1). Это убережет сервер от тяжелых запросов. Для простых случаев с ограниченной иерархией смело используйте левые соединения с ЕСТЬNULL (Решение 2) — это быстро, прозрачно и отлично интегрируется в СКД.

← На главную