В практике разработки на платформе 1С часто возникает задача: необходимо получить определенные данные (цены, скидки, условия обслуживания), которые в регистре сведений привязаны не к конкретной номенклатурной позиции, а к целой группе (папке). Проблема усложняется тем, что справочник Номенклатура может иметь неограниченное количество уровней иерархии, и скидка может быть установлена на любом из них.
Стандартный механизм запросов 1С не позволяет напрямую использовать оператор В ИЕРАРХИИ внутри условия ЛЕВОЕ СОЕДИНЕНИЕ. Мы не можем написать ЛЕВОЕ СОЕДИНЕНИЕ ... ПО Прайс.Номенклатура В ИЕРАРХИИ РегистрСкидок.Номенклатура. В этой статье мы подробно разберем, какими методами можно решить данную задачу, проанализируем их эффективность и выберем наиболее оптимальные для различных ситуаций.
Проанализируем самый простой, но наименее производительный способ. Суть его заключается в том, чтобы для каждой строки полученного прайса программно проверять наличие скидки у самой позиции, а затем у её родителя, родителя родителя и так далее до самого верха иерархии.
Рассмотрим пример кода, который реализует такую логику:
// Предположим, мы уже получили таблицу с прайсом в переменную ТЗПрайс
Для Каждого СтрокаПрайса Из ТЗПрайс Цикл
ТекущийОбъект = СтрокаПрайса.Номенклатура;
// Цикл вверх по иерархии
Пока ЗначениеЗаполнено(ТекущийОбъект) Цикл
// Пытаемся найти скидку для текущей номенклатуры или папки
Скидка = ПолучитьСкидкуИзРегистра(ТекущийОбъект);
Если Скидка <> 0 Тогда
СтрокаПрайса.Скидка = Скидка;
Прервать; // Скидка найдена, выходим из цикла по родителям
КонецЕсли;
// Переходим к следующему родителю
ТекущийОбъект = ТекущийОбъект.Родитель;
КонецЦикла;
КонецЦикла;
Плюсы метода: простота реализации и логическая понятность. Мы точно контролируем приоритет: сначала проверяем сам элемент, потом ближайшего родителя.
Минусы метода: крайне низкая скорость работы при больших объемах данных. Если в прайсе 10 000 строк, а глубина иерархии составляет 5 уровней, система может выполнить до 50 000 обращений к базе данных, что недопустимо для оперативных отчетов.
Если мы заранее знаем максимальную глубину вложенности справочника, мы можем попытаться решить задачу "в лоб" прямо в тексте запроса, обращаясь к полям Родитель. Посмотрим на пример такого запроса:
ВЫБРАТЬ
Прайс.Номенклатура КАК Номенклатура,
ЕСТЬNULL(СкидкиЭлемента.Значение,
ЕСТЬNULL(СкидкиРодителя1.Значение,
ЕСТЬNULL(СкидкиРодителя2.Значение, 0))) КАК ИтоговаяСкидка
ИЗ
Справочник.Номенклатура КАК Прайс
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Скидки КАК СкидкиЭлемента
ПО Прайс.Ссылка = СкидкиЭлемента.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Скидки КАК СкидкиРодителя1
ПО Прайс.Родитель = СкидкиРодителя1.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Скидки КАК СкидкиРодителя2
ПО Прайс.Родитель.Родитель = СкидкиРодителя2.Номенклатура
Проанализируем ситуацию: каждое обращение через точку (Родитель.Родитель) заставляет SQL-сервер создавать дополнительные соединения таблиц. Если уровней много, запрос становится громоздким и медленным. Кроме того, этот метод не универсален — если пользователь создаст папку на уровень глубже, запрос её "не увидит".
Выясним, как сделать это эффективно с помощью Системы компоновки данных. СКД обладает встроенным механизмом работы с иерархией, который оптимизирует запросы на уровне платформы. Разберем по шагам, как настроить такую связь:
Номенклатура.Номенклатура (или то поле в регистре, где хранится ссылка на папку).В этом случае СКД сама сформирует эффективные запросы к СУБД, используя внутренние индексы платформы для поиска по иерархии. Это работает значительно быстрее, чем вычисляемые поля с программными функциями.
Для решения задачи в рамках одного сложного запроса (без СКД) часто используют метод подготовки плоской таблицы соответствий. Мы можем заранее подготовить таблицу, в которой для каждого элемента будут перечислены все его родители. Рассмотрим структуру такой таблицы:
Элемент — ссылка на конечную номенклатуру.Предок — ссылка на самого себя или на любого из родителей.УровеньПредка — насколько глубоко находится этот предок (используется для приоритетов).Подготовив такую таблицу один раз во временную таблицу (ВТ), мы можем легко соединить её с регистром скидок:
ВЫБРАТЬ
ВТ_КартаИерархии.Элемент,
Скидки.Значение КАК Скидка,
ВТ_КартаИерархии.УровеньПредка
ПОМЕСТИТЬ ВТ_ВсеВозможныеСкидки
ИЗ
ВТ_КартаИерархии КАК ВТ_КартаИерархии
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.Скидки КАК Скидки
ПО ВТ_КартаИерархии.Предок = Скидки.Номенклатура
После этого нам останется лишь сгруппировать результат по Элементу и выбрать скидку с максимальным УровнемПредка. Это обеспечит выбор "самой близкой" скидки (например, если скидка установлена и на общую папку "Мебель", и на вложенную папку "Стулья", применится скидка от "Стульев").
Если записей в регистре скидок относительно немного, мы можем применить следующий алгоритм:
МассивПапокСоСкидками.ГДЕ Номенклатура В ИЕРАРХИИ (&МассивПапокСоСкидками).Этот метод заставляет 1С использовать внутреннее поле _Path (в терминах SQL), которое индексирует путь в дереве. Это один из самых быстрых способов фильтрации по иерархии в платформе 1С.
Подводя итог, можно дать следующие рекомендации: если вы строите сложный отчет, лучше всего использовать механизм связей наборов данных в СКД с включенной опцией иерархической связи. Если же результат нужен в коде (например, для заполнения документа), наиболее производительным будет метод с использованием оператора В ИЕРАРХИИ по заранее отобранному массиву папок или построение временной таблицы всех предков.
Избегайте использования циклов с обращением к базе данных (.Родитель.Родитель) внутри циклов по строкам товаров. Это критически важно для обеспечения масштабируемости вашей системы при росте справочника номенклатуры.