Как исправить ошибку «Неверные параметры "В ИЕРАРХИИ"» в запросе 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Каждый разработчик на платформе «1С:Предприятие» рано или поздно сталкивается со специфическими ограничениями языка запросов. Одно из наиболее распространенных и неочевидных ограничений связано с использованием оператора В ИЕРАРХИИ (IN HIERARCHY). При попытке выполнить, казалось бы, абсолютно логичный запрос, система внезапно возвращает ошибку компиляции: «Неверные параметры "В ИЕРАРХИИ"».

В этой статье мы подробно разберем, почему возникает данная ошибка, в чем кроется архитектурное ограничение платформы 1С и СУБД, а также рассмотрим несколько эффективных практических способов обхода этой проблемы с готовыми примерами кода.

Выясняем причину: почему платформа выдает ошибку?

Рассмотрим классическую ситуацию, с которой столкнулся автор темы. В коде обработчика события выполняется запрос к регистру сведений РегистрСведений.pustoshСопоставлениеОрганизацийДляКПК. Задача — определить, входит ли конкретный контрагент в группу, указанную в измерении этого регистра. Исходный код запроса выглядел следующим образом:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ОрганизацияНаКоторуюМеняем
    |ИЗ
    |	РегистрСведений.pustoshСопоставлениеОрганизацийДляКПК КАК Сопоставление
    |ГДЕ
    |	(ДатаНач >= &МояДатаНач)
    |	И (ДатаКон <= &МояДатаКон)
    |	И (Организация = &МояОрганизация)
    |	И (ВГруппеКонтрагентов = ИСТИНА)
    |	И (&Контрагент В ИЕРАРХИИ(Сопоставление.ГруппаКонтрагентов))"; // Источник ошибки!

При вызове метода Выполнить() платформа сообщает о неверных параметрах в иерархическом условии. Причина кроется в механизме трансляции запросов 1С в инструкции SQL.

Оператор В ИЕРАРХИИ — это специализированное расширение языка запросов 1С, предназначенное для работы с древовидными структурами справочников. На уровне СУБД (будь то MS SQL Server, PostgreSQL или IBM DB2) конструкции прямой поддержки иерархии в таком виде отсутствуют. Чтобы выполнить подобное условие, платформа 1С на лету преобразует оператор В ИЕРАРХИИ в сложную выборку, строящуюся на основе индексов иерархии (включая таблицы со связями родителей и потомков) либо генерирует массив условий с оператором OR.

Для того чтобы построить оптимальный и корректный план SQL-запроса, платформе необходимо знать конкретное значение «источника» иерархии заранее. Аргументом оператора В ИЕРАРХИИ может выступать только:

Использовать динамическое поле таблицы (в нашем случае — измерение ГруппаКонтрагентов из регистра сведений) в качестве аргумента оператора В ИЕРАРХИИ категорически запрещено. Поскольку значение этого поля меняется для каждой строки таблицы регистра, СУБД физически не сможет построить эффективный рекурсивный запрос для каждой записи «на лету» без колоссальной потери производительности. Платформа 1С блокирует такие конструкции еще на этапе синтаксического анализа.

Ниже мы подробно разберем три рабочих способа, как решить эту задачу правильно и обойти системное ограничение.

Решение 1. Метод инверсии логики (Поиск родителей)

Проанализируем логику нашей задачи. Нам нужно проверить, входит ли конкретный контрагент (&Контрагент) в группу, указанную в регистре. Вместо того чтобы заставлять СУБД для каждой строки регистра вычислять всю иерархию вниз от ГруппаКонтрагентов, мы можем пойти от обратного (инвертировать логику):

  1. Определить всю цепочку родителей для нашего целевого контрагента (от него самого до самого верхнего уровня папок справочника).
  2. Поместить этот список (массив) во временный параметр запроса.
  3. В запросе к регистру заменить тяжелый оператор В ИЕРАРХИИ на классический и быстрый оператор В (IN).

Посмотрим на пример реализации этого алгоритма в коде 1С:


// Шаг 1. Получаем список всех родителей для проверяемого контрагента
МассивРодителей = Новый Массив;
ТекущийКонтрагент = Источник.Контрагент;

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

// Шаг 2. Формируем и выполняем исправленный запрос
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Сопоставление.ОрганизацияНаКоторуюМеняем КАК Организация
    |ИЗ
    |	РегистрСведений.pustoshСопоставлениеОрганизацийДляКПК КАК Сопоставление
    |ГДЕ
    |	Сопоставление.ДатаНач <= &МояДата
    |	И Сопоставление.ДатаКон >= &МояДата
    |	И Сопоставление.Организация = &МояОрганизация
    |	И Сопоставление.ВГруппеКонтрагентов = ИСТИНА
    |	И Сопоставление.ГруппаКонтрагентов В (&МассивРодителей)"; // Быстрый поиск по индексу!

// Обратите внимание на правильную установку параметров дат (период действия)
Запрос.УстановитьПараметр("МояДата", Источник.Дата);
Запрос.УстановитьПараметр("МояОрганизация", Источник.Организация);
Запрос.УстановитьПараметр("МассивРодителей", МассивРодителей);

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    Сообщить("Найдена организация замены: " + Выборка.Организация);
КонецЕсли;

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

Решение 2. Использование пакета запросов и временных таблиц

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

Рассмотрим, как это реализовать по шагам:

  1. Выгружаем уникальные группы контрагентов из нашего регистра во временную таблицу.
  2. Используя левое или внутреннее соединение, сопоставляем справочник контрагентов с этой временной таблицей, применяя оператор В ИЕРАРХИИ к результату подзапроса временной таблицы.
  3. Выбираем итоговые данные.

Посмотрим на пример построения такого пакета запросов:


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

Запрос.УстановитьПараметр("МояОрганизация", Источник.Организация);
Запрос.УстановитьПараметр("СписокПроверяемыхКонтрагентов", СписокКонтрагентовИзДокумента);

РезультатЗапроса = Запрос.ВыполнитьПакет();

Важное примечание: Использование конструкции ПО (Таблица1.Поле В ИЕРАРХИИ (Таблица2.Поле)) поддерживается платформой 1С далеко не во всех версиях и СУБД. Настоятельно рекомендуется использовать первый способ (с получением родителей) во всех критически важных для производительности механизмах, так как сложные иерархические соединения могут приводить к блокировкам таблиц и неоптимальным планам запросов в СУБД.

Решение 3. Денормализация данных (для высоконагруженных систем)

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

В этом случае лучшим архитектурным решением будет денормализация данных. Выясним, как это работает:

  1. В справочник Контрагенты добавляется новый реквизит, например, ОсновнаяГруппаОпределенияПрав или признак категории. Если иерархия сложная, можно хранить ссылку на «Родителя 1-го уровня».
  2. При записи контрагента (в событии ПередЗаписью или ПриЗаписи) этот реквизит автоматически перезаполняется программным кодом, который находит нужного родителя.
  3. В регистре сведений сопоставление ведется не по произвольным динамическим группам, а жестко по значению этого реквизита.

Тогда ваш результирующий запрос превратится в простейшую и максимально быструю конструкцию:


Запрос.Текст = 
    "ВЫБРАТЬ
    |	Сопоставление.ОрганизацияНаКоторуюМеняем
    |ИЗ
    |	РегистрСведений.pustoshСопоставлениеОрганизацийДляКПК КАК Сопоставление
    |ГДЕ
    |	Сопоставление.ГруппаКонтрагентов = &УпрощеннаяГруппаКонтрагента";

Этот подход гарантирует стабильную скорость работы системы независимо от глубины вложенности папок и объема справочника контрагентов.

Резюме и рекомендации

Мы подробно проанализировали ошибку «Неверные параметры "В ИЕРАРХИИ"» в 1С. Подведем итоги совместной работы:

← На главную