При разработке отчетов или обработок на платформе 1С:Предприятие часто возникает задача: нужно выбрать данные из справочника (например, Номенклатура), но при этом предоставить пользователю выбор — либо отобрать данные по конкретной группе (иерархически), либо, если группа не выбрана, получить полный список элементов. На первый взгляд задача кажется тривиальной, но она скрывает в себе нюансы, связанные с типами данных, производительностью СУБД и чистотой кода.
В этой статье мы подробно разберем несколько подходов к решению этой задачи, проанализируем их сильные и слабые стороны, а также выясним, как правильно работать с параметрами запроса, чтобы избежать типичных ошибок начинающих разработчиков.
Рассмотрим ситуацию, с которой столкнулся автор темы. В коде использовалась тернарная операция для установки параметра:
Запрос.УстановитьПараметр("Группа", ?(ЗначениеЗаполнено(Группа), Группа, ""));
Проанализируем этот фрагмент. Здесь в случае, если переменная Группа пуста, в запрос передается пустая строка "". Однако в самом тексте запроса этот параметр сравнивается со ссылочным типом данных справочника Номенклатура. В системе 1С:Предприятие пустая строка и пустая ссылка — это абсолютно разные типы. Попытка использовать строку в условии В ИЕРАРХИИ приведет либо к ошибке выполнения, либо к неявному приведению типов, которое не даст ожидаемого результата (выбора всех элементов).
Первое и самое важное правило: если мы работаем со ссылочными типами в запросе, то и в параметры нужно передавать ссылки. Если группа не выбрана, мы должны передать пустую ссылку соответствующего типа. Рассмотрим исправленный программный код:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В ИЕРАРХИИ(&Группа)
| И НЕ Номенклатура.ЭтоГруппа";
// Вместо пустой строки передаем пустую ссылку справочника
ПустаяГруппа = Справочники.Номенклатура.ПустаяСсылка();
Запрос.УстановитьПараметр("Группа", ?(ЗначениеЗаполнено(Группа), Группа, ПустаяГруппа));
РезультатЗапроса = Запрос.Выполнить();
Как это работает: Платформа 1С обрабатывает условие В ИЕРАРХИИ специфическим образом. Если в качестве параметра передана пустая ссылка, система считает, что «родителем» является корень справочника. Так как все элементы справочника находятся в корне, условие В ИЕРАРХИИ с пустой ссылкой вернет все записи. Это решение является рабочим, но у него есть минус — производительность. Оператор В ИЕРАРХИИ на уровне SQL-запроса преобразуется в достаточно сложную конструкцию (часто с использованием временных таблиц), и выполнять ее для всей таблицы не всегда оптимально.
Чтобы сделать запрос более явным и избежать лишних вычислений иерархии там, где они не нужны, можно применить логическое условие непосредственно в секции ГДЕ. Посмотрим на пример кода:
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Номенклатура
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| (&Группа = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
| ИЛИ Номенклатура.Ссылка В ИЕРАРХИИ(&Группа))
| И НЕ Номенклатура.ЭтоГруппа";
Проанализируем это условие. Если параметр &Группа является пустой ссылкой, первая часть условия (до ИЛИ) становится истинной для всех строк таблицы. Вторая часть (проверка иерархии) в этом случае может даже не вычисляться оптимизатором СУБД. Если же группа заполнена, будет работать проверка вхождения в иерархию.
Важный нюанс: Такая конструкция может негативно сказаться на производительности («SARGability»). Оптимизатору СУБД сложнее построить оптимальный план выполнения (Index Seek) при наличии условий с ИЛИ, объединяющих константы и поля таблиц. На больших объемах данных это может привести к полному сканированию таблицы (Table Scan).
С точки зрения производительности СУБД, самым правильным подходом является исключение лишних условий из текста запроса еще до его выполнения. Разберем, как это сделать программно:
ТекстУсловия = "";
Если ЗначениеЗаполнено(Группа) Тогда
ТекстУсловия = "ГДЕ Номенклатура.Ссылка В ИЕРАРХИИ(&Группа) И НЕ Номенклатура.ЭтоГруппа";
Иначе
ТекстУсловия = "ГДЕ НЕ Номенклатура.ЭтоГруппа";
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Номенклатура.Ссылка КАК Номенклатура ИЗ Справочник.Номенклатура КАК Номенклатура " + ТекстУсловия;
Если ЗначениеЗаполнено(Группа) Тогда
Запрос.УстановитьПараметр("Группа", Группа);
КонецЕсли;
В этом случае мы передаем в СУБД максимально «чистый» запрос. Если фильтрация не нужна, серверу не придется проверять лишние логические условия или строить деревья иерархии. Для сложных запросов такой подход считается профессиональным стандартом.
Если вы разрабатываете отчет, лучше всего использовать встроенные механизмы СКД. В СКД задача решается установкой «Фигурных скобок» в тексте запроса:
ВЫБРАТЬ
Номенклатура.Ссылка КАК Номенклатура
ИЗ
Справочник.Номенклатура КАК Номенклатура
{ГДЕ
(Номенклатура.Ссылка В ИЕРАРХИИ (&Группа))}
В чем преимущество: Если пользователь не выберет значение параметра &Группа в настройках отчета, платформа 1С самостоятельно вырежет блок ГДЕ из итогового SQL-запроса. Это обеспечивает максимальную гибкость и производительность без написания дополнительного кода на встроенном языке.
Подведем итоги нашего анализа и выделим основные правила работы с необязательными фильтрами по иерархии:
Справочники.Номенклатура.ПустаяСсылка().В ИЕРАРХИИ(&ПустаяСсылка) вполне допустим и прост в реализации.СхемаЗапроса, чтобы не заставлять СУБД проверять лишние условия.Выяснив причину некорректной работы исходного кода, мы увидели, что решение заключается не просто в замене строки на ссылку, а в понимании того, как платформа и СУБД взаимодействуют друг с другом при выполнении запросов.