Как настроить отбор в регистре накопления по родителю справочника-измерения?

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

В процессе разработки в среде «1С:Предприятие» перед программистами часто встает задача фильтрации записей регистров накопления по косвенным признакам. Типичный пример такой задачи — необходимость получить остатки или обороты по регистру накопления, отфильтровав их по родителю (группе) справочника, который является измерением этого регистра. Рассмотрим, как решить эту задачу правильно, эффективно и в соответствии со стандартами разработки фирмы «1С».

Анализ проблемы и разбор неоптимального решения

Представим ситуацию: у нас есть регистр накопления Материалы, в котором ключевым измерением является Материал (ссылка на справочник «Номенклатура» или «Материалы»). Нам требуется выбрать данные только по тем материалам, которые входят в определенную группу (например, папка со всеми обрезками определенной категории или цвета). При этом структура партий или артикулов устроена так, что часть кода совпадает с родителем.

Иногда разработчики идут по пути наименьшего сопротивления и добавляют в сам регистр накопления дополнительное измерение — РодительМатериала. На первый взгляд, это решает проблему: теперь можно легко наложить прямой отбор по новому измерению. Однако такое решение является классическим антипаттерном (или, как выразились на форуме, «какашкокодом») по следующим причинам:

  1. Избыточность и раздувание базы данных. Измерения регистра накопления определяют уникальность его записей. Родитель справочника — это зависимый реквизит, значение которого всегда можно однозначно определить через ссылку на сам материал (Материал.Родитель). Дублирование этих данных в регистре приводит к необоснованному увеличению размера таблиц итогов и движений.
  2. Снижение производительности СУБД. Добавление лишнего измерения увеличивает размер индексов регистра. Это замедляет операции записи (проведение документов) и может приводить к избыточным блокировкам таблиц при параллельной работе пользователей.
  3. Сложность поддержки актуальности данных. Если пользователь перенесет элемент справочника из одной группы в другую, данные в регистре накопления станут неактуальными, так как в старых записях останется записан прежний родитель. Придется полностью перепроводить документы или писать обработку изменения движений.

Выясним, какие способы решения этой задачи существуют без изменения структуры регистра накопления.

Способ 1. Использование оператора В ИЕРАРХИИ в параметрах виртуальной таблицы

Самый простой и стандартный способ получить остатки или обороты по иерархическому родителю — использовать возможности языка запросов «1С». Для этого в параметрах виртуальной таблицы регистра (например, таблицы Остатки) применяется оператор В ИЕРАРХИИ.

Разберем по шагам, как выглядит такой запрос:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	МатериалыОстатки.Материал КАК Материал,
    |	МатериалыОстатки.КоличествоОстаток КАК Количество
    |ИЗ
    |	РегистрНакопления.Материалы.Остатки(&Период, Материал В ИЕРАРХИИ (&Родитель)) КАК МатериалыОстатки";

Запрос.УстановитьПараметр("Период", Новый Граница(КонецДня(ТекущаяДата()), ВидГраницы.Включая));
// Передаем ссылку на интересующую группу (папку) справочника "Материалы"
Запрос.УстановитьПараметр("Родитель", Справочники.Материалы.НайтиПоНаименованию("Окрашенные материалы")); 

РезультатЗапроса = Запрос.Выполнить();

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

Способ 2. Программное получение списка подчиненных элементов

Иногда разработчику требуется получить плоский список всех подчиненных элементов группы программно, например, для передачи в другие механизмы отбора или для анализа в коде. Попытка получить подчиненные элементы циклом или перебором методов менеджера справочника крайне неэффективна, так как порождает множество мелких запросов к базе данных.

Рассмотрим правильный способ получения списка элементов с помощью простого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Материалы.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.Материалы КАК Материалы
    |ГДЕ
    |	Материалы.Ссылка В ИЕРАРХИИ(&Родитель)
    |	И НЕ Материалы.ЭтоГруппа";

Запрос.УстановитьПараметр("Родитель", ВыбраннаяГруппаМатериалов);

// Выгружаем результат в массив для последующего использования в отборах
СписокПодчиненных = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");

Этот код вернет плоский массив ссылок на конечные элементы (исключая папки), который затем можно напрямую передать в параметры виртуальной таблицы регистра через оператор В.

Способ 3. Оптимизация для высоконагруженных систем (Использование ВТ)

Проанализируем производительность запросов. Прямое использование конструкции В ИЕРАРХИИ внутри параметров виртуальных таблиц (таких как ОстаткиИОбороты или Остатки) на базах данных с миллионами записей может вызывать существенные замедления. СУБД строит неоптимальный план запроса, особенно если в параметр &Родитель по ошибке передается пустая ссылка (тогда система пытается обойти все дерево справочника).

Для оптимизации работы высоконагруженных систем эксперты рекомендуют сначала собрать список элементов во временную таблицу (ВТ), проиндексировать ее, а затем соединить с виртуальной таблицей регистра или использовать оператор В.

Посмотрим на пример оптимизированного запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Материалы.Ссылка КАК Ссылка
    |ПОМЕСТИТЬ ВТ_ВыбранныеМатериалы
    |ИЗ
    |	Справочник.Материалы КАК Материалы
    |ГДЕ
    |	Материалы.Ссылка В ИЕРАРХИИ(&Родитель)
    |	И НЕ Материалы.ЭтоГруппа
    |
    |ИНДЕКСИРОВАТЬ ПО
    |	Ссылка
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	МатериалыОстатки.Материал КАК Материал,
    |	МатериалыОстатки.КоличествоОстаток КАК Количество
    |ИЗ
    |	РегистрНакопления.Материалы.Остатки(&Период, Материал В (ВЫБРАТЬ Ссылка ИЗ ВТ_ВыбранныеМатериалы)) КАК МатериалыОстатки";

Запрос.УстановитьПараметр("Период", МоментВремени());
Запрос.УстановитьПараметр("Родитель", ВыбраннаяГруппаМатериалов);

РезультатЗапроса = Запрос.Выполнить();

Индексирование временной таблицы ВТ_ВыбранныеМатериалы позволяет СУБД мгновенно сопоставить данные и выдать результат без лишнего сканирования таблиц итогов регистра.

Способ 4. Использование Системы компоновки данных (СКД)

Если задача заключается в построении отчета или выводе данных пользователю на форму, лучшим решением будет использование Системы компоновки данных (СКД). В СКД все сложные алгоритмы иерархических отборов уже реализованы на уровне платформы.

Разработчику достаточно выполнить следующие шаги:

  1. Создать схему компоновки данных и добавить в нее набор данных-запрос к регистру накопления (без жестких условий в параметрах виртуальной таблицы).
  2. На вкладке «Настройки» добавить элемент отбора по полю Материал.
  3. В качестве вида сравнения для этого отбора выбрать «В группе» (для одного родителя) или «В группе из списка» (для нескольких групп).

Платформа «1С:Предприятие» при формировании отчета автоматически преобразует этот отбор в максимально оптимизированный SQL-запрос с учетом иерархической структуры данных.

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

← На главную