Как правильно сформировать динамическое условие в запросе 1С и избежать лишнего оператора «ИЛИ»?

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

При динамической генерации текста запросов в цикле разработчики часто сталкиваются с синтаксической ошибкой: в конце сформированного блока условий остается висеть лишний логический оператор ИЛИ (или И). В результате платформа выдает ошибку синтаксиса при попытке выполнить запрос.

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

Вариант 1. Использование логического префикса «ГДЕ ЛОЖЬ»

Проанализируем самый лаконичный и популярный алгоритмический прием. Вместо того чтобы вычислять последний элемент и убирать замыкающий оператор, мы ставим условие ИЛИ перед каждым проверяемым выражением, а в начале секции ГДЕ объявляем нейтральный логический литерал ЛОЖЬ.

Посмотрим на пример реализации:


Запрос.Текст = 
"ВЫБРАТЬ
|	ПартииТоваров.Регистратор,
|	ПартииТоваров.КоличествоОборот КАК Количество
|ИЗ
|	РегистрНакопления.ПартииТоваровНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, Регистратор, , ) КАК ПартииТоваров
|ГДЕ
|	ЛОЖЬ";

Для Каждого ЭлементСписка Из СписокОбрабатываемыхДокументов Цикл
	Запрос.Текст = Запрос.Текст + " ИЛИ ПартииТоваров.Регистратор ССЫЛКА Документ." + ЭлементСписка.Значение;
КонецЦикла;

В этом случае итоговая строка будет иметь вид ГДЕ ЛОЖЬ ИЛИ Условие1 ИЛИ Условие2 .... Так как выражение ЛОЖЬ ИЛИ X логически эквивалентно X, результат отбора не искажается, а код цикла остается минимальным и простым.

Вариант 2. Проверка первого или последнего прохода цикла

Если по каким-либо причинам добавление литерала ЛОЖЬ не подходит, разберем программное отслеживание итераций. На практике отслеживать первый проход проще и безопаснее, чем вычислять последний, особенно если обход выполняется по коллекции с помощью Для Каждого.

Посмотрим на реализацию с флагом первого прохода:


ЭтоПервыйПроход = Истина;
ТекстУсловия = "";

Для Каждого ЭлементСписка Из СписокОбрабатываемыхДокументов Цикл
	ТекстУсловия = ТекстУсловия + ?(ЭтоПервыйПроход, "", " ИЛИ ") 
		+ "ПартииТоваров.Регистратор ССЫЛКА Документ." + ЭлементСписка.Значение;
	ЭтоПервыйПроход = Ложь;
КонецЦикла;

Запрос.Текст = Запрос.Текст + " ГДЕ " + ТекстУсловия;

Если же используется цикл по числовому индексу, можно проверить равенство индекса нулю или конечному значению:


КоличествоЭлементов = СписокОбрабатываемыхДокументов.Количество();
Для Индекс = 0 По КоличествоЭлементов - 1 Цикл
	Разделитель = ?(Индекс = 0, "", " ИЛИ ");
	Запрос.Текст = Запрос.Текст + Разделитель 
		+ "ПартииТоваров.Регистратор ССЫЛКА Документ." + СписокОбрабатываемыхДокументов[Индекс].Значение;
КонецЦикла;

Вариант 3. Использование массива и функции «СтрСоединить»

Разберем современный стандарт разработки в 1С: формирование массива подстрок с последующим объединением через функцию СтрСоединить. Этот метод избавляет от условий внутри цикла и делает код максимально читаемым.

Посмотрим на пример:


МассивУсловий = Новый Массив;

Для Каждого ЭлементСписка Из СписокОбрабатываемыхДокументов Цикл
	МассивУсловий.Добавить("ПартииТоваров.Регистратор ССЫЛКА Документ." + ЭлементСписка.Значение);
КонецЦикла;

Если МассивУсловий.Количество() > 0 Тогда
	ТекстОтбора = СтрСоединить(МассивУсловий, " ИЛИ ");
	Запрос.Текст = СтрЗаменить(Запрос.Текст, "&УсловиеПоРегистраторам", ТекстОтбора);
КонецЕсли;

Здесь также применяется хорошая практика: в базовом тексте запроса оставляется маркер (например, &УсловиеПоРегистраторам), который затем заменяется готовой строкой с помощью функции СтрЗаменить. Это позволяет открывать и редактировать базовый запрос в стандартном Конструкторе запросов.

Вариант 4. Обрезка лишних символов после цикла

Выясним, как устранить ошибку строковыми функциями, если цикл уже сформировал строку с завершающим оператором ИЛИ .


ДлинаРазделителя = СтрДлина(" ИЛИ ");
Если Прав(Запрос.Текст, ДлинаРазделителя) = " ИЛИ " Тогда
	Запрос.Текст = Лев(Запрос.Текст, СтрДлина(Запрос.Текст) - ДлинаРазделителя);
КонецЕсли;

Вариант 5. Оптимальное архитектурное решение: «ТИПЗНАЧЕНИЯ» и параметры

Проанализируем техническую сторону выполнения подобных запросов в СУБД. Конкатенация условий вида ССЫЛКА Документ.А ИЛИ Регистратор ССЫЛКА Документ.Б порождает неоптимальный SQL-код. При большом количестве видов документов оптимизатор СУБД часто отказывается от использования индексов и выполняет полное сканирование таблицы.

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

Рассмотрим, как решить эту задачу правильно без динамической склейки текста, передав массив типов через параметр:


МассивТипов = Новый Массив;
Для Каждого ЭлементСписка Из СписокОбрабатываемыхДокументов Цикл
	МассивТипов.Добавить(Тип("ДокументСсылка." + ЭлементСписка.Значение));
КонецЦикла;

Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	ПартииТоваров.Регистратор,
|	ПартииТоваров.КоличествоОборот КАК Количество
|ИЗ
|	РегистрНакопления.ПартииТоваровНаСкладах.ОстаткиИОбороты(
|		&НачалоПериода, 
|		&КонецПериода, 
|		Регистратор, 
|		, 
|		ТИПЗНАЧЕНИЯ(Регистратор) В (&МассивТипов)
|	) КАК ПартииТоваров";

Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Запрос.УстановитьПараметр("МассивТипов", МассивТипов);

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

Рекомендации по производительности

  1. Фильтры виртуальных таблиц: Условия на регистратор или измерения всегда необходимо помещать в параметры самой виртуальной таблицы (в круглых скобках), а не в общую секцию ГДЕ. Это предотвращает расчет итогов по всей таблице перед фильтрацией.
  2. Использование параметров вместо строк: Конструкция ТИПЗНАЧЕНИЯ(Поле) В (&МассивТипов) транслируется в эффективный SQL-фильтр по полю составного типа (..._TYPE / ..._RTRef) и позволяет СУБД повторно применять готовый план запроса.
  3. Безопасность метаданных: При формировании списка документов по строковым именам проверяйте их существование через коллекцию Метаданные.Документы.Найти(...), чтобы исключить падение запроса при изменении конфигурации.
← На главную