В процессе разработки в среде «1С:Предприятие» перед программистами часто встает задача фильтрации записей регистров накопления по косвенным признакам. Типичный пример такой задачи — необходимость получить остатки или обороты по регистру накопления, отфильтровав их по родителю (группе) справочника, который является измерением этого регистра. Рассмотрим, как решить эту задачу правильно, эффективно и в соответствии со стандартами разработки фирмы «1С».
Представим ситуацию: у нас есть регистр накопления Материалы, в котором ключевым измерением является Материал (ссылка на справочник «Номенклатура» или «Материалы»). Нам требуется выбрать данные только по тем материалам, которые входят в определенную группу (например, папка со всеми обрезками определенной категории или цвета). При этом структура партий или артикулов устроена так, что часть кода совпадает с родителем.
Иногда разработчики идут по пути наименьшего сопротивления и добавляют в сам регистр накопления дополнительное измерение — РодительМатериала. На первый взгляд, это решает проблему: теперь можно легко наложить прямой отбор по новому измерению. Однако такое решение является классическим антипаттерном (или, как выразились на форуме, «какашкокодом») по следующим причинам:
Материал.Родитель). Дублирование этих данных в регистре приводит к необоснованному увеличению размера таблиц итогов и движений.Выясним, какие способы решения этой задачи существуют без изменения структуры регистра накопления.
Самый простой и стандартный способ получить остатки или обороты по иерархическому родителю — использовать возможности языка запросов «1С». Для этого в параметрах виртуальной таблицы регистра (например, таблицы Остатки) применяется оператор В ИЕРАРХИИ.
Разберем по шагам, как выглядит такой запрос:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| МатериалыОстатки.Материал КАК Материал,
| МатериалыОстатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.Материалы.Остатки(&Период, Материал В ИЕРАРХИИ (&Родитель)) КАК МатериалыОстатки";
Запрос.УстановитьПараметр("Период", Новый Граница(КонецДня(ТекущаяДата()), ВидГраницы.Включая));
// Передаем ссылку на интересующую группу (папку) справочника "Материалы"
Запрос.УстановитьПараметр("Родитель", Справочники.Материалы.НайтиПоНаименованию("Окрашенные материалы"));
РезультатЗапроса = Запрос.Выполнить();
При таком подходе СУБД самостоятельно развернет иерархическое дерево справочника и выберет остатки только по тем элементам, которые входят в указанную группу или ее подгруппы. Структура регистра при этом остается чистой и лаконичной.
Иногда разработчику требуется получить плоский список всех подчиненных элементов группы программно, например, для передачи в другие механизмы отбора или для анализа в коде. Попытка получить подчиненные элементы циклом или перебором методов менеджера справочника крайне неэффективна, так как порождает множество мелких запросов к базе данных.
Рассмотрим правильный способ получения списка элементов с помощью простого запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Материалы.Ссылка КАК Ссылка
|ИЗ
| Справочник.Материалы КАК Материалы
|ГДЕ
| Материалы.Ссылка В ИЕРАРХИИ(&Родитель)
| И НЕ Материалы.ЭтоГруппа";
Запрос.УстановитьПараметр("Родитель", ВыбраннаяГруппаМатериалов);
// Выгружаем результат в массив для последующего использования в отборах
СписокПодчиненных = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
Этот код вернет плоский массив ссылок на конечные элементы (исключая папки), который затем можно напрямую передать в параметры виртуальной таблицы регистра через оператор В.
Проанализируем производительность запросов. Прямое использование конструкции В ИЕРАРХИИ внутри параметров виртуальных таблиц (таких как ОстаткиИОбороты или Остатки) на базах данных с миллионами записей может вызывать существенные замедления. СУБД строит неоптимальный план запроса, особенно если в параметр &Родитель по ошибке передается пустая ссылка (тогда система пытается обойти все дерево справочника).
Для оптимизации работы высоконагруженных систем эксперты рекомендуют сначала собрать список элементов во временную таблицу (ВТ), проиндексировать ее, а затем соединить с виртуальной таблицей регистра или использовать оператор В.
Посмотрим на пример оптимизированного запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Материалы.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_ВыбранныеМатериалы
|ИЗ
| Справочник.Материалы КАК Материалы
|ГДЕ
| Материалы.Ссылка В ИЕРАРХИИ(&Родитель)
| И НЕ Материалы.ЭтоГруппа
|
|ИНДЕКСИРОВАТЬ ПО
| Ссылка
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| МатериалыОстатки.Материал КАК Материал,
| МатериалыОстатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.Материалы.Остатки(&Период, Материал В (ВЫБРАТЬ Ссылка ИЗ ВТ_ВыбранныеМатериалы)) КАК МатериалыОстатки";
Запрос.УстановитьПараметр("Период", МоментВремени());
Запрос.УстановитьПараметр("Родитель", ВыбраннаяГруппаМатериалов);
РезультатЗапроса = Запрос.Выполнить();
Индексирование временной таблицы ВТ_ВыбранныеМатериалы позволяет СУБД мгновенно сопоставить данные и выдать результат без лишнего сканирования таблиц итогов регистра.
Если задача заключается в построении отчета или выводе данных пользователю на форму, лучшим решением будет использование Системы компоновки данных (СКД). В СКД все сложные алгоритмы иерархических отборов уже реализованы на уровне платформы.
Разработчику достаточно выполнить следующие шаги:
Материал.Платформа «1С:Предприятие» при формировании отчета автоматически преобразует этот отбор в максимально оптимизированный SQL-запрос с учетом иерархической структуры данных.
Таким образом, мы детально разобрали, почему добавление реквизита родителя непосредственно в регистр накопления является архитектурной ошибкой, и изучили четыре профессиональных способа решения этой задачи с помощью встроенного языка, языка запросов и СКД.