Как реализовать поиск скидки по иерархии групп номенклатуры в запросе 1С

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

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

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

Разбор типичной ошибки: запрос в цикле

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


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

Почему это плохо? Если в документе 100 строк, система выполнит 100 отдельных обращений к базе данных. Это создает колоссальную нагрузку на сервер и замедляет работу пользователя. В программировании 1С использование Запрос.Выполнить() внутри цикла считается одной из самых грубых ошибок проектирования. Наша задача — получить все данные одним запросом.

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

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

Разберем по шагам создание оптимального запроса:

  1. Создаем временную таблицу ВТ_Товары, куда выгружаем всю номенклатуру из заказа.
  2. Создаем временную таблицу ВТ_Скидки, куда выбираем актуальные записи из регистра РегистрСведений.СкидкиАкции.
  3. Соединяем эти таблицы, используя фильтр по иерархии.

ВЫБРАТЬ
    ЗаказТовары.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
    Документ.ЗаказПокупателя.Товары КАК ЗаказТовары
ГДЕ
    ЗаказТовары.Ссылка = &Ссылка;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Скидки.ГруппаТоваров КАК ГруппаТоваров,
    Скидки.Скидка КАК ПроцентСкидки
ПОМЕСТИТЬ ВТ_Скидки
ИЗ
    РегистрСведений.СкидкиАкции.СрезПоследних(&ДатаАктуальности, Прайс = &ТипЦен) КАК Скидки;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Товары.Номенклатура,
    ВТ_Скидки.ПроцентСкидки
ИЗ
    ВТ_Товары КАК ВТ_Товары,
    ВТ_Скидки КАК ВТ_Скидки
ГДЕ
    ВТ_Товары.Номенклатура В ИЕРАРХИИ (ВТ_Скидки.ГруппаТоваров)

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

Решение 2: Получение всех родителей товара (профессиональный подход)

Если справочник Номенклатура имеет огромный объем, а скидок в регистре тоже много, предыдущий вариант может работать медленно из-за специфики выполнения В ИЕРАРХИИ. В таких случаях мы применим метод "от обратного": выясним, в какие группы входит каждый товар, и поищем скидки именно по этим группам.

Выясним причину, почему это эффективно: поиск по конкретному списку ссылок (оператор В) работает значительно быстрее, чем иерархический поиск.

Разберем алгоритм реализации:

  1. Выгружаем всю номенклатуру из документа в массив.
  2. Для каждого товара программно получаем список всех его вышестоящих родителей. Это можно сделать в цикле по метаданным или простым запросом к полям Родитель, Родитель.Родитель и так далее (если глубина иерархии ограничена и известна).
  3. Формируем общий массив всех уникальных групп, имеющих отношение к нашим товарам.
  4. Одним запросом получаем все скидки для этих групп.
  5. В коде сопоставляем товар и найденную скидку.

Пример кода для получения родителей (упрощенно):


СписокГрупп = Новый Массив;
Для Каждого Стр Из Товары Цикл
    ТекущийРодитель = Стр.Номенклатура.Родитель;
    Пока ЗначениеЗаполнено(ТекущийРодитель) Цикл
        Если СписокГрупп.Найти(ТекущийРодитель) = Неопределено Тогда
            СписокГрупп.Добавить(ТекущийРодитель);
        КонецЕсли;
        ТекущийРодитель = ТекущийРодитель.Родитель;
    КонецЦикла;
КонецЦикла;

Затем передаем СписокГрупп в запрос в качестве параметра &МассивГрупп и используем условие ГДЕ ГруппаТоваров В (&МассивГрупп).

Проблема приоритетов уровней иерархии

Проанализируем ситуацию: скидка 5% установлена на группу "Бытовая техника", а скидка 10% — на подгруппу "Холодильники". Какой процент должен получить товар "Холодильник Минск"?

Обычно действует правило: наиболее близкая к товару скидка является приоритетной. Чтобы реализовать это в запросе, нам нужно знать "уровень" каждой группы. При получении родителей можно вычислять их уровень вложенности. При сопоставлении данных мы выберем ту запись, у которой уровень вложенности максимальный (самый глубокий).

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

Рекомендации по оптимизации базы данных

Если подобные расчеты проводятся часто (например, при каждом подборе товара в корзину), рассмотрим дополнительные методы ускорения:

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

← На главную