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

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

В практике разработки на платформе 1С:Предприятие 8 часто возникает задача получения настроек или параметров, которые могут быть заданы как для конкретного элемента справочника, так и для его родительских групп. Типичный пример — «Ограничение скидки», «Счета учета» или «Ценовые группы». Сложность заключается в том, что система должна найти наиболее «близкое» значение: если настройка есть у самого элемента — берем её, если нет — ищем у ближайшего родителя, и так далее до самого верха иерархии.

Особенно остро проблема встает при обработке больших массивов данных (от 20 000 до 50 000 элементов) и высокой частоте запросов (например, каждые 2 минуты). В таких условиях простые переборы в цикле приведут к критическому падению производительности и блокировкам базы данных. В этой статье мы проанализируем различные подходы и разберем наиболее эффективное решение, используемое в типовых конфигурациях.

Способ 1: Простой перебор родителей в цикле

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

Пример реализации такого подхода:


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

Важное замечание: Этот метод категорически не подходит для массовой обработки данных. Если у вас 50 000 позиций, и каждая имеет глубину иерархии 5 уровней, вы рискуете выполнить до 250 000 запросов к базе данных, что недопустимо.

Способ 2: Использование плоской таблицы иерархии в запросе

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

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

  1. Создаем временную таблицу ВТ_Иерархия, содержащую колонки: Элемент, Связь (сам элемент или его родитель) и Уровень.
  2. Для каждого элемента из входного массива добавляем в таблицу строки для него самого (уровень 0) и для всех его родителей (уровень 1, 2, ...).
  3. Выполняем левое соединение ВТ_Иерархия с регистром сведений по полю Связь = Номенклатура.
  4. Группируем результат по полю Элемент и выбираем запись с минимальным значением Уровень.

Этот метод позволяет избежать рекурсивных запросов и использует мощь SQL-сервера для выбора нужных данных.

Способ 3: Реализация на основе механизмов типовых конфигураций (БП 3.0)

В современных конфигурациях 1С, таких как «Бухгалтерия предприятия» или ERP, механизм получения счетов учета реализован именно через построение иерархических соответствий. Рассмотрим, как мы можем адаптировать эту логику для нашей задачи. Ключевым моментом здесь является использование функции ПолучитьСписокВышеСтоящихГруппЭлементов (или аналогичной), которая возвращает массив всех родителей для списка ссылок.

Ниже представлен пример кода, адаптированный для получения ограничений скидок:


// Подготовим таблицу иерархии
ТаблицаИерархии = Новый ТаблицаЗначений;
ТаблицаИерархии.Колонки.Добавить("Элемент", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаИерархии.Колонки.Добавить("Родитель", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаИерархии.Колонки.Добавить("Уровень", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(10, 0)));

// СоответствиеЭлементовИГрупп - функция, возвращающая массив родителей для каждой ссылки
СоответствиеЭлементовИГрупп = ПолучитьСписокВышеСтоящихГруппЭлементов(СписокНоменклатуры);

Для каждого Номенклатура Из СписокНоменклатуры Цикл
    // Добавляем самого себя (Уровень 0 - наивысший приоритет)
    НоваяСтрока = ТаблицаИерархии.Добавить();
    НоваяСтрока.Элемент = Номенклатура;
    НоваяСтрока.Родитель = Номенклатура;
    НоваяСтрока.Уровень = 0;
    
    СписокГрупп = СоответствиеЭлементовИГрупп.Получить(Номенклатура);
    Если СписокГрупп <> Неопределено Тогда
        Для Индекс = 0 По СписокГрупп.Количество() - 1 Цикл
            НоваяСтрока = ТаблицаИерархии.Добавить();
            НоваяСтрока.Элемент = Номенклатура;
            НоваяСтрока.Родитель = СписокГрупп[Индекс];
            // Чем дальше родитель, тем больше уровень
            НоваяСтрока.Уровень = Индекс + 1;
        КонецЦикла;
    КонецЕсли;
КонецЦикла;

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

Метод денормализации данных: «Материализованный путь»

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

Преимущества:

Риски:

Для реализации этого метода рекомендуется использовать Подписки на события: ПриЗаписи справочника Номенклатура (для отслеживания смены родителя) и ПриЗаписи регистра сведений (для каскадного обновления настроек).

Оптимизация производительности при больших массивах

Выясним, как избежать блокировок и тормозов при обработке 50 000 элементов каждые 2 минуты. Рассмотрим несколько практических советов:

  1. Индексация: Убедитесь, что измерение Номенклатура в регистре сведений проиндексировано. Это база для быстрого поиска.
  2. Менеджер временных таблиц: При работе с большими входящими массивами сначала загружайте их во временную таблицу через МенеджерВременныхТаблиц. SQL-сервер гораздо эффективнее обрабатывает соединения JOIN между двумя таблицами, чем условие В (&Массив).
  3. Пакетные запросы: Старайтесь объединять подготовку данных и итоговую выборку в один пакетный запрос. Это сокращает количество обращений к серверу БД.
  4. Кэширование: Если данные регистра меняются редко, используйте механизм ПовторноеИспользованиеЗначений или кэшируйте результат во временном хранилище на стороне сервера. Перед расчетом проверяйте «дату последнего изменения» регистра, чтобы понять, нужно ли обновлять кэш.

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

← На главную