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