Как в запросе 1С получить скидки для номенклатуры, если они заданы для папок-родителей?

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

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

Стандартный механизм запросов 1С не позволяет напрямую использовать оператор В ИЕРАРХИИ внутри условия ЛЕВОЕ СОЕДИНЕНИЕ. Мы не можем написать ЛЕВОЕ СОЕДИНЕНИЕ ... ПО Прайс.Номенклатура В ИЕРАРХИИ РегистрСкидок.Номенклатура. В этой статье мы подробно разберем, какими методами можно решить данную задачу, проанализируем их эффективность и выберем наиболее оптимальные для различных ситуаций.

Метод 1. Программный обход иерархии в цикле

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

Рассмотрим пример кода, который реализует такую логику:


// Предположим, мы уже получили таблицу с прайсом в переменную ТЗПрайс
Для Каждого СтрокаПрайса Из ТЗПрайс Цикл
    ТекущийОбъект = СтрокаПрайса.Номенклатура;
    
    // Цикл вверх по иерархии
    Пока ЗначениеЗаполнено(ТекущийОбъект) Цикл
        // Пытаемся найти скидку для текущей номенклатуры или папки
        Скидка = ПолучитьСкидкуИзРегистра(ТекущийОбъект); 
        
        Если Скидка <> 0 Тогда
            СтрокаПрайса.Скидка = Скидка;
            Прервать; // Скидка найдена, выходим из цикла по родителям
        КонецЕсли;
        
        // Переходим к следующему родителю
        ТекущийОбъект = ТекущийОбъект.Родитель;
    КонецЦикла;
КонецЦикла;

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

Минусы метода: крайне низкая скорость работы при больших объемах данных. Если в прайсе 10 000 строк, а глубина иерархии составляет 5 уровней, система может выполнить до 50 000 обращений к базе данных, что недопустимо для оперативных отчетов.

Метод 2. Использование фиксированных соединений "через точку"

Если мы заранее знаем максимальную глубину вложенности справочника, мы можем попытаться решить задачу "в лоб" прямо в тексте запроса, обращаясь к полям Родитель. Посмотрим на пример такого запроса:


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

Проанализируем ситуацию: каждое обращение через точку (Родитель.Родитель) заставляет SQL-сервер создавать дополнительные соединения таблиц. Если уровней много, запрос становится громоздким и медленным. Кроме того, этот метод не универсален — если пользователь создаст папку на уровень глубже, запрос её "не увидит".

Метод 3. Связи наборов данных в СКД (рекомендуемый способ)

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

  1. Создадим первый Набор данных - запрос, который выбирает основной прайс-лист.
  2. Создадим второй Набор данных - запрос, который выбирает все записи из регистра скидок.
  3. Перейдем на закладку Связи наборов данных.
  4. Добавим связь: Источник — Набор1 (Прайс), Приемник — Набор2 (Скидки).
  5. В поле "Выражение источника" укажем Номенклатура.
  6. В поле "Выражение приемника" укажем Номенклатура (или то поле в регистре, где хранится ссылка на папку).
  7. Важный момент: установим флаг Связь по иерархии.

В этом случае СКД сама сформирует эффективные запросы к СУБД, используя внутренние индексы платформы для поиска по иерархии. Это работает значительно быстрее, чем вычисляемые поля с программными функциями.

Метод 4. Создание временной таблицы "Карта иерархии"

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

Подготовив такую таблицу один раз во временную таблицу (ВТ), мы можем легко соединить её с регистром скидок:


ВЫБРАТЬ
    ВТ_КартаИерархии.Элемент,
    Скидки.Значение КАК Скидка,
    ВТ_КартаИерархии.УровеньПредка
ПОМЕСТИТЬ ВТ_ВсеВозможныеСкидки
ИЗ
    ВТ_КартаИерархии КАК ВТ_КартаИерархии
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.Скидки КАК Скидки
        ПО ВТ_КартаИерархии.Предок = Скидки.Номенклатура

После этого нам останется лишь сгруппировать результат по Элементу и выбрать скидку с максимальным УровнемПредка. Это обеспечит выбор "самой близкой" скидки (например, если скидка установлена и на общую папку "Мебель", и на вложенную папку "Стулья", применится скидка от "Стульев").

Метод 5. Оптимизация через оператор В ИЕРАРХИИ и массив папок

Если записей в регистре скидок относительно немного, мы можем применить следующий алгоритм:

  1. Сначала отдельным запросом выберем все уникальные папки и элементы из регистра скидок в массив: МассивПапокСоСкидками.
  2. В основном запросе используем условие: ГДЕ Номенклатура В ИЕРАРХИИ (&МассивПапокСоСкидками).

Этот метод заставляет 1С использовать внутреннее поле _Path (в терминах SQL), которое индексирует путь в дереве. Это один из самых быстрых способов фильтрации по иерархии в платформе 1С.

Резюме для реализации

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

Избегайте использования циклов с обращением к базе данных (.Родитель.Родитель) внутри циклов по строкам товаров. Это критически важно для обеспечения масштабируемости вашей системы при росте справочника номенклатуры.

← На главную