При работе в платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с ограничениями стандартного языка запросов, особенно когда дело касается работы с иерархическими справочниками. Типичная задача — отбор документов по контрагенту. Однако логика усложняется, если в документах в качестве контрагента может быть указан как конкретный элемент, так и целая группа (папка) контрагентов, а в качестве фильтра в отчете пользователь выбирает конкретного контрагента (элемент). Нам необходимо выбрать все документы, где указан либо сам этот контрагент, либо группа, в которую он входит.
В этой статье мы подробно разберем, почему стандартное иерархическое сравнение здесь не работает напрямую, проанализируем популярные ошибки и рассмотрим наиболее эффективные методы решения этой задачи с точки зрения производительности базы данных.
Стандартный оператор запроса Условие(Контрагент в выбКонтрагент) отлично работает в направлении «сверху вниз»: когда в качестве выбКонтрагент передается группа, а в документах лежат элементы. Платформа автоматически разворачивает это условие и находит все элементы, входящие в указанную группу.
В нашей задаче ситуация обратная (направление «снизу вверх»):
Контрагент может быть выбрана группа (например, «Оптовые покупатели»).выбКонтрагент выбран конкретный элемент (например, «ИП Иванов», который находится внутри группы «Оптовые покупатели»).Если мы напишем Условие(выбКонтрагент в Контрагент);, запрос не выдаст ожидаемого результата, так как синтаксис 1С 7.7 не рассчитан на автоматический обратный поиск по дереву групп при таком написании.
---
Один из участников обсуждения предложил использовать проверку принадлежности к группе непосредственно внутри конструкции Условие():
ТекстЗапроса = "
|Период с ВыбНачПериода по ВыбКонПериода;
|Контрагент = Документ.Дисконт.Контрагент;
|Номенклатура = Документ.Дисконт.Номенклатура;
|Бонус = Документ.Дисконт.Бонус;
|Функция Минимум = Минимум(Бонус);
|Группировка Номенклатура без групп;
|Условие(выбКонтрагент.ПринадлежитГруппе(Контрагент) = 1);";
Этот код действительно вернет документы, в которых указана группа, включающая нашего контрагента. Однако у этого подхода есть три критических недостатка, из-за которых его не рекомендуется использовать в реальных рабочих базах:
ПринадлежитГруппе(). На больших объемах данных это приведет к зависанию отчета.выбКонтрагент.ПринадлежитГруппе(Контрагент) вернет 0, так как элемент не может входить сам в себя как в группу. Чтобы отчет работал корректно в обоих случаях, условие придется усложнить:
Условие((выбКонтрагент = Контрагент) ИЛИ (выбКонтрагент.ПринадлежитГруппе(Контрагент) = 1));
Контрагент окажется не заполнен, выполнение метода на пустом значении может вызвать ошибку времени выполнения.---
Логичным развитием идеи является предварительный сбор всех родителей выбранного контрагента в Список Значений (СЗ) перед запуском запроса с последующей фильтрацией через оператор В:
// Предварительно собираем родителей контрагента
сзКонтрагенты = СоздатьОбъект("СписокЗначений");
сзКонтрагенты.ДобавитьЗначение(выбКонтрагент);
ВремРодитель = выбКонтрагент.Родитель;
Пока ВремРодитель.Выбран() = 1 Цикл
сзКонтрагенты.ДобавитьЗначение(ВремРодитель);
ВремРодитель = ВремРодитель.Родитель;
КонецЦикла;
Затем в запросе пишется условие:
|Условие(Контрагент в сзКонтрагенты);
Внимание, здесь кроется опасная ловушка платформы 1С 7.7!
Когда оператор В работает со списком значений, и в этом списке оказывается хотя бы одна группа, платформа начинает выполнять иерархическую проверку для этой группы.
Пример: Наш контрагент «ИП Иванов» лежит в группе «Оптовики». Мы добавили в список значений самого «ИП Иванова» и группу «Оптовики». При выполнении запроса условие Контрагент в сзКонтрагенты вернет нам документы по «ИП Иванову» и по группе «Оптовики» (что правильно). Но оно также вернет документы по «ООО Ромашка», которое просто находится в той же папке «Оптовики»! Платформа увидит группу в списке значений и посчитает любое совпадение по иерархии истинным. В результате мы получим некорректные и избыточные данные в отчете.
---
Чтобы обойти проблему иерархического «отравления» списка значений и при этом сохранить высокую скорость выполнения запроса (без перехода в «черный запрос»), необходимо полностью отказаться от оператора В в пользу строгого сравнения на равенство (=).
Мы должны программно построить цепочку условий ИЛИ, основываясь на родителях выбранного элемента:
// Собираем массив или список родителей
СписокРодителей = СоздатьОбъект("СписокЗначений");
СписокРодителей.ДобавитьЗначение(выбКонтрагент);
ВремРодитель = выбКонтрагент.Родитель;
Пока ВремРодитель.Выбран() = 1 Цикл
СписокРодителей.ДобавитьЗначение(ВремРодитель);
ВремРодитель = ВремРодитель.Родитель;
КонецЦикла;
// Динамически формируем строку условия для запроса
СтрокаУсловия = "";
Для Ном = 1 По СписокРодителей.РазмерСписка() Цикл
ТекЭлемент = СписокРодителей.ПолучитьЗначение(Ном);
Если Ном > 1 Тогда
СтрокаУсловия = СтрокаУсловия + " ИЛИ ";
КонецЕсли;
СтрокаУсловия = СтрокаУсловия + "(Контрагент = СписокРодителей.ПолучитьЗначение(" + Ном + "))";
КонецЦикла;
// Формируем текст запроса
ТекстЗапроса = "
|Период с ВыбНачПериода по ВыбКонПериода;
|Контрагент = Документ.Дисконт.Контрагент;
|Номенклатура = Документ.Дисконт.Номенклатура;
|Бонус = Документ.Дисконт.Бонус;
|Функция Минимум = Минимум(Бонус);
|Группировка Номенклатура без групп;
|Условие(" + СтрокаУсловия + ");";
Почему это работает быстро и правильно?
=), СУБД эффективно использует индексы таблиц, запрос выполняется на сервере БД мгновенно.---
В крупных базах данных на платформе 1С 7.7 для решения подобных задач иерархического поиска «снизу вверх» принято использовать прямые запросы к СУБД (например, через внешнюю компоненту 1С++ и класс ODBCRecordset).
Прямой SQL-запрос позволяет решить задачу максимально лаконично:
выбКонтрагент и всех его родителей.IN.Пример структуры SQL-условия:
WHERE DocTable.SP_Counterparty_ID IN (SELECT ParentID FROM #TempParentIDs)
Поскольку стандартный SQL-оператор IN ничего не знает об особенностях иерархии в понимании 1С и сравнивает только точные текстовые/ссылочные идентификаторы, данный запрос отработает без ложных срабатываний и со скоростью, недоступной стандартному механизму запросов 1С 7.7.
Для решения задачи проверки иерархии снизу вверх в 1С 7.7:
ПринадлежитГруппе() внутри условий запроса на больших базах.В из-за ложных срабатываний по всей иерархии группы.