При разработке механизмов ценообразования, систем скидок или правил печати документов программисты 1С часто сталкиваются с необходимостью хранить настройки не для конкретных элементов справочника, а для целых групп. Например, в регистре сведений может быть измерение Контрагент, где записана группа «Оптовые покупатели». Когда мы пытаемся проверить в запросе, действует ли это правило для конкретного «Иванова», возникают сложности с синтаксисом.
В этой статье мы подробно разберем, почему стандартный подход не работает и какие существуют эффективные способы решения этой задачи.
Рассмотрим ситуацию: у нас есть искомый клиент (параметр &ПарамКлиент) и таблица регистра, где в поле Контрагент может лежать либо сам клиент, либо его родительская группа. Интуитивно хочется написать так:
ГДЕ
&ПарамКлиент В ИЕРАРХИИ (Регистр.Контрагент)
При попытке выполнить такой запрос платформа выдаст ошибку: «Неверные параметры "В ИЕРАРХИИ"». Выясним причину: в языке запросов 1С оператор В ИЕРАРХИИ работает строго в одном направлении. Он ожидает, что слева находится поле таблицы, а справа — конкретное значение или массив значений (параметр), для которых платформа должна построить дерево вниз. Платформа не умеет для каждой строки таблицы «на лету» строить дерево вверх, чтобы проверить, является ли значение в колонке родителем нашего параметра.
Рассмотрим рабочие способы обхода этого ограничения.
Проанализируем самый надежный и производительный метод. Вместо того чтобы заставлять запрос искать иерархию, мы передадим ему уже готовый список всех «покровителей» нашего элемента. Если «Иванов» входит в группу «Мелкий опт», которая входит в группу «Оптовики», то массив будет содержать: [Иванов, Мелкий опт, Оптовики].
Для получения этого списка можно использовать простой цикл, так как глубина иерархии в справочниках редко превышает 5–10 уровней. Разберем пример кода:
// Получаем всех родителей элемента
МассивРодителей = Новый Массив;
ТекущаяСсылка = ПараметрОбъект; // Наш элемент (например, Контрагент)
Пока ЗначениеЗаполнено(ТекущаяСсылка) Цикл
МассивРодителей.Добавить(ТекущаяСсылка);
ТекущаяСсылка = ТекущаяСсылка.Родитель;
КонецЦикла;
// Теперь передаем этот массив в запрос
Запрос.УстановитьПараметр("МассивРодителей", МассивРодителей);
Теперь текст самого запроса становится максимально простым и эффективным:
ВЫБРАТЬ
Правила.ВидДокумента,
Правила.Контрагент
ИЗ
РегистрСведений.лк_ПравилаПечатиМаркЛиста КАК Правила
ГДЕ
Правила.Контрагент В (&МассивРодителей)
Этот метод работает быстрее всего, так как использует обычный индекс по измерению Контрагент и простое сравнение IN в SQL.
Иногда требуется получить список родителей прямо в коде запроса, не обращаясь к объектной модели. Для этого можно использовать конструкцию ИТОГИ ПО ССЫЛКА ТОЛЬКО ИЕРАРХИЯ. Посмотрим на этот прием подробнее:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Ссылка
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
Контрагенты.Ссылка = &ПарамКлиент
ИТОГИ ПО
Ссылка ТОЛЬКО ИЕРАРХИЯ
Важное ограничение: результат такого запроса нельзя поместить во временную таблицу (ВТ), так как ИТОГИ в ВТ не поддерживаются. Вам придется сначала выполнить этот запрос, выгрузить результат в массив в коде 1С, а затем передать этот массив во второй запрос. Это делает данный способ менее удобным, чем «Способ 1».
Проанализируем ситуацию, когда для одного клиента может найтись сразу несколько правил: одно задано для него лично, другое — для его группы, третье — для всех контрагентов (корень справочника). Какое правило выбрать?
Логично предположить, что наиболее специфичное правило (установленное для самого элемента) важнее общего правила для группы. Чтобы реализовать это в запросе, мы можем добавить поле Уровень. Посмотрим, как это сделать:
ВЫБРАТЬ ПЕРВЫЕ 1
Правила.ВидДокумента,
Правила.Контрагент,
Правила.Контрагент.Уровень КАК Уровень
ИЗ
РегистрСведений.лк_ПравилаПечатиМаркЛиста КАК Правила
ГДЕ
Правила.Контрагент В (&МассивРодителей)
УПОРЯДОЧИТЬ ПО
Уровень УБЫВ
Таким образом, если в регистре есть запись и для группы (уровень 2), и для элемента (уровень 5), система корректно выберет запись для элемента.
В высоконагруженных системах или при очень большой глубине иерархии иногда используют специфические приемы:
Использование «Полного кода»: Если в справочнике настроена уникальность кодов и иерархическое представление (строка вида «001/005/012»), можно использовать текстовое сравнение. Рассмотрим логику: если полный код нашего элемента 0001/0045/0098, то любой его родитель будет иметь код, являющийся началом этой строки. В запросе это можно проверить через ПОДОБНО, но это требует хранения «Полного пути» в отдельном реквизите и обычно считается «костылем».
Матрица входимости: Создается вспомогательный регистр сведений, где хранятся пары Элемент — Предок для всех уровней. Это позволяет делать связь (Join) в запросе напрямую, без использования массивов и иерархических операторов. Это самый мощный метод для сложных аналитических отчетов, но он требует поддержания актуальности данных в этой матрице при каждом переносе элементов в справочнике.
Для большинства задач в 1С оптимальным является Способ №1. Он прост в реализации, не создает нагрузки на сервер базы данных и легко читается другими разработчиками. Достаточно собрать родителей элемента в массив через свойство .Родитель в цикле и передать этот массив в параметр запроса. Это гарантирует корректную работу условия вхождения в группу вне зависимости от ограничений языка запросов.