При разработке отчетов и обработок в 1С часто возникает задача: есть список родительских групп (например, из табличной части документа или параметра) и необходимо получить фактические данные по всем подчиненным элементам номенклатуры с привязкой к этим группам. Прямое соединение таблиц по условию равенства не подтягивает вложенные элементы нижних уровней, а попытка написать перебор через Номенклатура.Родитель.Родитель... приводит к получению неоптимального запроса и резкому падению производительности.
Рассмотрим, почему возникает эта проблема, какие ограничения платформы 1С стоит учитывать и как правильно связать элементы иерархии с произвольным списком родителей.
Разберем типичную ошибку при формировании запроса, когда связывание элементов справочника с родителями выполняется через оператор ИЛИ по всем возможным уровням вложенности:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ГруппыНоменклатуры.ГруппаНоменклатуры
ПОМЕСТИТЬ Родители
ИЗ
&ГруппыНоменклатуры КАК ГруппыНоменклатуры;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Номенклатура.Ссылка,
Номенклатура.Родитель,
Родители.ГруппаНоменклатуры
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ Родители КАК Родители
ПО (Родители.ГруппаНоменклатуры = Номенклатура.Родитель)
ИЛИ (Родители.ГруппаНоменклатуры = Номенклатура.Родитель.Родитель)
ИЛИ (Родители.ГруппаНоменклатуры = Номенклатура.Родитель.Родитель.Родитель)
ИЛИ (Родители.ГруппаНоменклатуры = Номенклатура.Родитель.Родитель.Родитель.Родитель)
ГДЕ
Номенклатура.Ссылка В ИЕРАРХИИ(&ГруппыНоменклатуры)
И Номенклатура.ЭтоГруппа = ЛОЖЬ;
Проанализируем, почему такой код работает крайне медленно:
ИЛИ в секции соединения ПО: СУБД не может задействовать индексы по полю Родитель, что заставляет сервер выполнять сканирование всей таблицы (Table Scan).В ИЕРАРХИИ: Платформа 1С не позволяет применять конструкцию В ИЕРАРХИИ непосредственно внутри условий соединения ПО, а только в секции ГДЕ или ИМЕЮЩИЕ.Если задача решается в Системе компоновки данных (СКД), наилучшим и самым производительным вариантом является разделение запроса на два набора данных с последующей настройкой связи между ними.
Разберем реализацию этого способа по шагам:
ВЫБРАТЬ РАЗЛИЧНЫЕ
СТ_ПланыПродаж.План КАК План,
СТ_ПланыПродаж.ГруппаНоменклатуры КАК ГруппаНоменклатуры
ИЗ
Документ.СТ_ПланыПродаж.Планы КАК СТ_ПланыПродаж
ГДЕ
СТ_ПланыПродаж.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода;
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.СуммаОборот КАК СуммаFact
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , Номенклатура В ИЕРАРХИИ (&ГруппаНоменклатуры)) КАК ПродажиОбороты;
ГруппаНоменклатурыГруппаНоменклатурыВ этом случае СКД автоматически выполнит запрос Набора 2 для каждой группы из Набора 1, подставляя значение группы в параметр иерархии, что гарантирует высокую скорость работы и корректный выбор всех подчиненных элементов.
Когда вычисление требуется выполнить в одном запросе без применения механизмов СКД, используют подход, называемый транзитивным замыканием. Суть метода заключается в каскадном объединении таблиц сдвига уровней с помощью последовательных левых соединений по полю Родитель.
Рассмотрим пример каскадного получения всех вышестоящих родителей для каждого элемента справочника Справочник.Номенклатура:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Элемент,
ЕСТЬNULL(Уровень1.Родитель, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Родитель1,
ЕСТЬNULL(Уровень2.Родитель, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Родитель2,
ЕСТЬNULL(Уровень3.Родитель, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК Родитель3
ПОМЕСТИТЬ ТЗ_Иерархия
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Уровень1
ПО Номенклатура.Родитель = Уровень1.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Уровень2
ПО Уровень1.Родитель = Уровень2.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Уровень3
ПО Уровень2.Родитель = Уровень3.Ссылка
ГДЕ
НЕ Номенклатура.ЭтоГруппа;
Каждый следующий каскад удваивает разворачиваемое количество уровней вложенности. Развернув иерархию во временную таблицу, мы можем легко выполнить точное соединение по полю равенства (без операторов ИЛИ), получив высокую скорость обработки даже на больших объемах данных.
Если задача заключается в получении суммовых показателей по иерархии без жесткой привязки к документу плана, достаточно сформировать стандартный запрос по элементам и применить секцию ИТОГИ:
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.СуммаОборот КАК Сумма
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , ) КАК ПродажиОбороты
ИТОГИ
СУММА(Сумма)
ПО
Номенклатура ИЕРАРХИЯ;
Платформа 1С на уровне СУБД самостоятельно рассчитает агрегированные данные для всех родительских групп, избавив от необходимости ручного связывания уровней.