Как правильно использовать «Условие В» в запросах 1С 7.7 при проверке иерархии снизу вверх?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При работе в платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с ограничениями стандартного языка запросов, особенно когда дело касается работы с иерархическими справочниками. Типичная задача — отбор документов по контрагенту. Однако логика усложняется, если в документах в качестве контрагента может быть указан как конкретный элемент, так и целая группа (папка) контрагентов, а в качестве фильтра в отчете пользователь выбирает конкретного контрагента (элемент). Нам необходимо выбрать все документы, где указан либо сам этот контрагент, либо группа, в которую он входит.

В этой статье мы подробно разберем, почему стандартное иерархическое сравнение здесь не работает напрямую, проанализируем популярные ошибки и рассмотрим наиболее эффективные методы решения этой задачи с точки зрения производительности базы данных.

Суть проблемы: Иерархия «снизу вверх»

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

В нашей задаче ситуация обратная (направление «снизу вверх»):

Если мы напишем Условие(выбКонтрагент в Контрагент);, запрос не выдаст ожидаемого результата, так как синтаксис 1С 7.7 не рассчитан на автоматический обратный поиск по дереву групп при таком написании.

---

Вариант 1. Использование метода ПринадлежитГруппе() (Простой, но медленный способ)

Один из участников обсуждения предложил использовать проверку принадлежности к группе непосредственно внутри конструкции Условие():


ТекстЗапроса = "
|Период с ВыбНачПериода по ВыбКонПериода;
|Контрагент = Документ.Дисконт.Контрагент;
|Номенклатура = Документ.Дисконт.Номенклатура;
|Бонус = Документ.Дисконт.Бонус;
|Функция Минимум = Минимум(Бонус);
|Группировка Номенклатура без групп;
|Условие(выбКонтрагент.ПринадлежитГруппе(Контрагент) = 1);";

Этот код действительно вернет документы, в которых указана группа, включающая нашего контрагента. Однако у этого подхода есть три критических недостатка, из-за которых его не рекомендуется использовать в реальных рабочих базах:

  1. Катастрофическое падение производительности (Режим «Черного запроса»): База данных (как SQL, так и DBF) не умеет выполнять методы объектов 1С внутри СУБД. В результате платформа 1С 7.7 вынуждена отказаться от оптимизации запроса. Вместо этого она считывает абсолютно все записи документов из базы данных в локальную память клиента и для каждой строки последовательно выполняет функцию ПринадлежитГруппе(). На больших объемах данных это приведет к зависанию отчета.
  2. Логическая ошибка при совпадении элементов: Если в документе выбран конкретный контрагент («ИП Иванов»), а не группа, метод выбКонтрагент.ПринадлежитГруппе(Контрагент) вернет 0, так как элемент не может входить сам в себя как в группу. Чтобы отчет работал корректно в обоих случаях, условие придется усложнить:
    
    Условие((выбКонтрагент = Контрагент) ИЛИ (выбКонтрагент.ПринадлежитГруппе(Контрагент) = 1));
    
    Это делает выполнение запроса еще более медленным.
  3. Проблема пустых значений: Если в каком-то документе реквизит Контрагент окажется не заполнен, выполнение метода на пустом значении может вызвать ошибку времени выполнения.

---

Вариант 2. Сбор родителей в Список Значений и скрытая ловушка оператора «В»

Логичным развитием идеи является предварительный сбор всех родителей выбранного контрагента в Список Значений (СЗ) перед запуском запроса с последующей фильтрацией через оператор В:


// Предварительно собираем родителей контрагента
сзКонтрагенты = СоздатьОбъект("СписокЗначений");
сзКонтрагенты.ДобавитьЗначение(выбКонтрагент);

ВремРодитель = выбКонтрагент.Родитель;
Пока ВремРодитель.Выбран() = 1 Цикл
    сзКонтрагенты.ДобавитьЗначение(ВремРодитель);
    ВремРодитель = ВремРодитель.Родитель;
КонецЦикла;

Затем в запросе пишется условие:

|Условие(Контрагент в сзКонтрагенты);

Внимание, здесь кроется опасная ловушка платформы 1С 7.7!

Когда оператор В работает со списком значений, и в этом списке оказывается хотя бы одна группа, платформа начинает выполнять иерархическую проверку для этой группы.

Пример: Наш контрагент «ИП Иванов» лежит в группе «Оптовики». Мы добавили в список значений самого «ИП Иванова» и группу «Оптовики». При выполнении запроса условие Контрагент в сзКонтрагенты вернет нам документы по «ИП Иванову» и по группе «Оптовики» (что правильно). Но оно также вернет документы по «ООО Ромашка», которое просто находится в той же папке «Оптовики»! Платформа увидит группу в списке значений и посчитает любое совпадение по иерархии истинным. В результате мы получим некорректные и избыточные данные в отчете.

---

Вариант 3. Динамическое построение цепочки равенств через «ИЛИ» (Оптимальное решение для стандартных запросов)

Чтобы обойти проблему иерархического «отравления» списка значений и при этом сохранить высокую скорость выполнения запроса (без перехода в «черный запрос»), необходимо полностью отказаться от оператора В в пользу строгого сравнения на равенство (=).

Мы должны программно построить цепочку условий ИЛИ, основываясь на родителях выбранного элемента:


// Собираем массив или список родителей
СписокРодителей = СоздатьОбъект("СписокЗначений");
СписокРодителей.ДобавитьЗначение(выбКонтрагент);

ВремРодитель = выбКонтрагент.Родитель;
Пока ВремРодитель.Выбран() = 1 Цикл
    СписокРодителей.ДобавитьЗначение(ВремРодитель);
    ВремРодитель = ВремРодитель.Родитель;
КонецЦикла;

// Динамически формируем строку условия для запроса
СтрокаУсловия = "";
Для Ном = 1 По СписокРодителей.РазмерСписка() Цикл
    ТекЭлемент = СписокРодителей.ПолучитьЗначение(Ном);
    Если Ном > 1 Тогда
        СтрокаУсловия = СтрокаУсловия + " ИЛИ ";
    КонецЕсли;
    СтрокаУсловия = СтрокаУсловия + "(Контрагент = СписокРодителей.ПолучитьЗначение(" + Ном + "))";
КонецЦикла;

// Формируем текст запроса
ТекстЗапроса = "
|Период с ВыбНачПериода по ВыбКонПериода;
|Контрагент = Документ.Дисконт.Контрагент;
|Номенклатура = Документ.Дисконт.Номенклатура;
|Бонус = Документ.Дисконт.Бонус;
|Функция Минимум = Минимум(Бонус);
|Группировка Номенклатура без групп;
|Условие(" + СтрокаУсловия + ");";

Почему это работает быстро и правильно?

---

Вариант 4. Использование прямых SQL-запросов (Для высоконагруженных систем)

В крупных базах данных на платформе 1С 7.7 для решения подобных задач иерархического поиска «снизу вверх» принято использовать прямые запросы к СУБД (например, через внешнюю компоненту 1С++ и класс ODBCRecordset).

Прямой SQL-запрос позволяет решить задачу максимально лаконично:

  1. С помощью рекурсивного SQL-запроса (или временной таблицы) мы мгновенно получаем плоский список идентификаторов (ID) самого выбКонтрагент и всех его родителей.
  2. Передаем этот плоский список в результирующий SQL-запрос через оператор IN.

Пример структуры SQL-условия:

WHERE DocTable.SP_Counterparty_ID IN (SELECT ParentID FROM #TempParentIDs)

Поскольку стандартный SQL-оператор IN ничего не знает об особенностях иерархии в понимании 1С и сравнивает только точные текстовые/ссылочные идентификаторы, данный запрос отработает без ложных срабатываний и со скоростью, недоступной стандартному механизму запросов 1С 7.7.

Резюме

Для решения задачи проверки иерархии снизу вверх в 1С 7.7:

← На главную