При работе с запросами в платформе «1С:Предприятие» разработчики часто сталкиваются с иерархическими справочниками. Использование оператора В ИЕРАРХИИ является стандартным способом отбора данных с учетом вложенности элементов. Однако при попытке использовать пустую ссылку в качестве параметра этого оператора возникает странный на первый взгляд парадокс:
Ссылка В ИЕРАРХИИ (ПустаяСсылка) всегда возвращает значение ИСТИНА для любого существующего элемента.ПустаяСсылка В ИЕРАРХИИ (ПустаяСсылка) внезапно возвращает значение ЛОЖЬ.В рамках этой статьи мы подробно разберем, почему так происходит, заглянем «под капот» СУБД, проанализируем катастрофическое влияние таких запросов на производительность системы и рассмотрим лучшие практики по их оптимизации.
Для того чтобы понять логику работы платформы 1С при вычислении иерархии, нам необходимо изучить, как иерархические данные хранятся на уровне СУБД. Платформа 1С использует классическую реляционную модель Adjacency List (список смежности).
В физической таблице справочника (например, контрагентов или номенклатуры) для каждого элемента выделено поле Родитель (на уровне СУБД это поле имеет имя _ParentIDRRef). В этом поле хранится уникальный идентификатор (GUID) родительской группы:
Родитель записывается GUID этой группы.Родитель записывается техническое пустое значение (GUID, состоящий из одних нулей — 00000000-0000-0000-0000-000000000000), то есть Пустая ссылка.Проанализируем ситуацию, когда мы выполняем условие Ссылка В ИЕРАРХИИ (ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)) для любого реального элемента:
Родитель и видит там пустую ссылку.Теперь выясним причину, почему для самой пустой ссылки результат будет противоположным. Когда мы проверяем ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка) В ИЕРАРХИИ (ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)):
Ссылка (_IDRRef) заполнено нулями.В современных СУБД (MS SQL Server, PostgreSQL) нет прямого оператора В ИЕРАРХИИ. Платформа 1С транслирует этот оператор через собственный внутренний язык запросов SDBL в сложную конструкцию СУБД. Обычно это рекурсивные обобщенные табличные выражения (Recursive CTE) или создание временных таблиц с итерационным циклом наполнения.
Когда мы передаем конкретную группу в качестве параметра (например, &ГруппаКонтрагентов), СУБД быстро находит эту группу и выбирает только её подчиненные элементы. Глубина поиска жестко ограничена рамками этой группы.
Но если мы передаем в иерархию Пустую ссылку, для СУБД это означает следующее:
Если в вашем справочнике Номенклатура или Контрагенты содержатся десятки или сотни тысяч записей, такой запрос гарантированно приведет к следующим последствиям:
rphost) или СУБД может аварийно завершиться по нехватке памяти (Out of Memory).Именно поэтому среди профессиональных разработчиков 1С действует негласное, но жесткое правило: никогда не допускать выполнения оператора В ИЕРАРХИИ с пустой ссылкой!
Рассмотрим подробнее, как обойти эту проблему в реальных задачах автоматизации. Предположим, у нас есть отчет, в котором пользователь может выбрать группу контрагентов для отбора. Если пользователь не выбрал группу (параметр пуст), мы должны вывести всех контрагентов без ограничения по иерархии.
Этот способ основан на особенностях работы оптимизаторов современных СУБД. Если в условии ИЛИ левая часть возвращает истину, то правая часть даже не начинает вычисляться. Посмотрим на пример кода запроса:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
Контрагенты.Представление КАК Представление
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
&ПараметрГруппа = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
ИЛИ Контрагенты.Ссылка В ИЕРАРХИИ (&ПараметрГруппа)
Разберем по шагам, как работает это условие:
&ПараметрГруппа равен пустой ссылке.&ПараметрГруппа = ЗНАЧЕНИЕ(...) возвращает ИСТИНА.Контрагенты.Ссылка В ИЕРАРХИИ (&ПараметрГруппа). Полного обхода дерева не происходит, запрос выполняется мгновенно!Этот способ аналогичен первому, но вместо сравнения с пустой ссылкой внутри запроса мы передаем готовый булев флаг, указывающий на то, заполнен ли отбор:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Товар
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
НЕ &ОтборПоставлен
ИЛИ Номенклатура.Ссылка В ИЕРАРХИИ (&ВыбраннаяГруппа)
В коде инициализации запроса на встроенном языке мы пишем следующую логику:
Запрос = Новый Запрос;
Запрос.Текст = "..." // Текст запроса выше
ОтборПоставлен = ЗначениеЗаполнено(ПараметрГруппа);
Запрос.УстановитьПараметр("ОтборПоставлен", ОтборПоставлен);
Запрос.УстановитьПараметр("ВыбраннаяГруппа", ПараметрГруппа);
Результат = Запрос.Выполнить();
Если &ОтборПоставлен равен Ложь, левая часть условия НЕ &ОтборПоставлен превращается в ИСТИНА, и СУБД вновь игнорирует выполнение рекурсивного поиска по иерархии.
Если мы пишем сложный алгоритм во встроенном языке, наилучшим решением с точки зрения производительности СУБД будет полное исключение условия из текста запроса, если параметр не заполнен. Рассмотрим пример рефакторинга кода из обсуждения на форуме:
// Подготавливаем базовый текст запроса
ТекстЗапроса =
"ВЫБРАТЬ
| Взаиморасчеты.Контрагент КАК Контрагент,
| Взаиморасчеты.СуммаОстаток КАК Остаток
|ИЗ
| РегистрНакопления.Взаиморасчеты.Остатки КАК Взаиморасчеты
|{ГДЕ_УСЛОВИЕ_ФИЛЬТРА}";
// Анализируем заполненность отбора
Если ЗначениеЗаполнено(ОтборГруппаКонтрагентов) Тогда
Условие = "ГДЕ Взаиморасчеты.Контрагент В ИЕРАРХИИ(&ОтборГруппа)";
Запрос.УстановитьПараметр("ОтборГруппа", ОтборГруппаКонтрагентов);
Иначе
// Если отбор не задан, мы просто вырезаем условие из запроса
Условие = "";
КонецЕсли;
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "{ГДЕ_УСЛОВИЕ_ФИЛЬТРА}", Условие);
Запрос.Текст = ТекстЗапроса;
Этот способ является наиболее чистым с точки зрения формируемого SQL-кода, так как СУБД вообще не получает лишних логических ветвлений и выполняет максимально простой и эффективный план запроса.
Если вы разрабатываете отчет, настоятельно рекомендуется отказаться от жесткого прописывания условий В ИЕРАРХИИ в тексте запроса. Вместо этого используйте возможности Системы компоновки данных (СКД):
ГДЕ для иерархических полей.Контрагент с типом сравнения В иерархии.Мы выяснили, что странное поведение условий с пустой ссылкой в иерархии — это не баг платформы 1С, а логическое следствие реляционной структуры хранения данных и модели списка смежности. Передача пустой ссылки в оператор В ИЕРАРХИИ заставляет СУБД строить дерево для всего справочника, что приводит к критическому падению производительности.
Используйте безопасное конструирование условий с оператором ИЛИ, динамически управляйте текстом запроса на встроенном языке или доверяйте построение отборов Системе компоновки данных. Эти простые правила позволят вашим базам данных работать быстро и стабильно даже на миллионных массивах информации.