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