Как использовать оператор ПОДОБНО для массива значений в запросе 1С?

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

При разработке на платформе «1С:Предприятие 8» программисты часто сталкиваются с необходимостью отбора данных по частичному совпадению. Если критерий поиска один, задача решается классическим использованием оператора ПОДОБНО в связке с параметром-шаблоном. Однако ситуация существенно усложняется, когда на входе у нас имеется целый массив неполных кодов или наименований, и нам нужно найти все записи, которые соответствуют хотя бы одному из шаблонов в этом массиве.

В этой статье мы подробно разберем, как решить эту задачу в запросах 1С, проанализируем основные подходы, их преимущества, скрытые угрозы безопасности и критические проблемы производительности СУБД. Мы также изучим официальные стандарты разработки фирмы «1С», которые уберегут ваш код от ошибок.

Рассмотрим по шагам два ключевых метода реализации данной задачи:

  1. Динамическое формирование текста запроса с объединением условий по оператору ИЛИ.
  2. Использование временной таблицы параметров и связи по условию ПОДОБНО (с учетом требований стандартов 1С).

---

Способ №1. Динамическое построение условий в тексте запроса

Этот способ основан на генерации текста запроса во встроенном языке 1С непосредственно перед его выполнением. Вместо того чтобы пытаться передать массив в один параметр, мы программно обходим исходный массив шаблонов и для каждого элемента дописываем в секцию ГДЕ дополнительное условие через логическое ИЛИ.

Проанализируем пример кода, реализующий этот подход:


// Исходный массив неполных кодов
МассивШаблонов = Новый Массив;
МассивШаблонов.Добавить("00-001%");
МассивШаблонов.Добавить("00-005%");
МассивШаблонов.Добавить("00-012%");

// Базовый текст запроса с плейсхолдером для динамического условия
ТекстЗапроса = 
"ВЫБРАТЬ
|	Товары.Ссылка КАК Ссылка,
|	Товары.Код КАК Код
|ИЗ
|	Справочник.Номенклатура КАК Товары
|ГДЕ
|	&ДинамическоеУсловие";

ТекстУсловия = "";
ПараметрыЗапроса = Новый Структура;

// Программная сборка условий
Для Индекс = 0 По МассивШаблонов.ВГРАНИЦА() Цикл
	ИмяПараметра = "Шаблон_" + Формат(Индекс, "ЧГ=0");
	СтрокаШаблона = МассивШаблонов[Индекс];
	
	// Формируем динамическое имя параметра для защиты от SQL-инъекций
	Если ПустаяСтрока(ТекстУсловия) Тогда
		ТекстУсловия = "Товары.Код ПОДОБНО &" + ИмяПараметра;
	Иначе
		ТекстУсловия = ТекстУсловия + " ИЛИ Товары.Код ПОДОБНО &" + ИмяПараметра;
	EndЕсли;
	
	ПараметрыЗапроса.Вставить(ИмяПараметра, СтрокаШаблона);
КонецЦикла;

// Если массив оказался пуст, подставим заведомо ложное условие
Если ПустаяСтрока(ТекстУсловия) Тогда
	ТекстУсловия = "ЛОЖЬ";
КонецЕсли;

// Подставляем сформированное условие в текст запроса
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&ДинамическоеУсловие", ТекстУсловия);

// Инициализация и выполнение запроса
Запрос = Новый Запрос(ТекстЗапроса);
Для Каждого КлючИЗначение Из ПараметрыЗапроса Цикл
	Запрос.УстановитьПараметр(КлючИЗначение.Ключ, КлючИЗначение.Значение);
КонецЦикла;

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

Преимущества динамического способа:

Недостатки динамического способа:

---

Способ №2. Использование связи по ПОДОБНО через временную таблицу

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

Однако при реализации этого способа разработчики часто совершают грубую ошибку, нарушающую официальные стандарты разработки 1С.

Как делать НЕЛЬЗЯ (Нарушение стандарта № std726)

Рассмотрим ошибочный вариант запроса, который часто предлагают на форумах:


// ТАК ДЕЛАТЬ НЕЛЬЗЯ!
ВЫБРАТЬ ВТ_Шаблоны.Код КАК Маска
ПОМЕСТИТЬ ВТ_Фильтр
ИЗ &ТаблицаШаблонов КАК ВТ_Шаблоны;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ Товары.Ссылка
ИЗ Справочник.Номенклатура КАК Товары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК ВТ_Фильтр
	ПО Товары.Код ПОДОБНО "%" + ВТ_Фильтр.Маска

Согласно требованиям стандарта 1С «Особенности использования в запросах оператора ПОДОБНО», правым операндом этого оператора может выступать исключительно строковая константа (например, "001%") либо один параметр запроса (например, &Маска).

Запрещено конструировать маску подобия внутри запроса путем конкатенации строк, сложения полей разных таблиц или вычислений. Такое поведение не только нарушает стандарты и вызывает предупреждения в автоматических системах проверки кода (1C:EDT, SonarQube), но и приводит к фатальным синтаксическим ошибкам при работе базы данных на СУБД IBM DB2, Oracle или устаревших версиях MS SQL Server.

Как сделать правильно и безопасно

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

Разберем правильный алгоритм работы по шагам:

Шаг 1. Создание и подготовка таблицы значений. Мы создаем таблицу значений с типизированной колонкой для масок поиска и заполняем её строками, уже содержащими спецсимволы маскирования (символ %).


// Создаем таблицу значений с типизацией колонок
ТаблицаФильтра = Новый ТаблицаЗначений;
ОписаниеТиповСтрока = Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(50));
ТаблицаФильтра.Колонки.Добавить("Маска", ОписаниеТиповСтрока);

// Заполняем таблицу, добавляя спецсимволы "%" на стороне встроенного языка
Для Каждого ЗначениеИзМассива Из МассивНеполныхКодов Цикл
	НоваяСтрока = ТаблицаФильтра.Добавить();
	// Например, мы ищем коды, которые НАЧИНАЮТСЯ со значений из массива
	НоваяСтрока.Маска = СокрЛП(ЗначениеИзМассива) + "%";
КонецЦикла;

Шаг 2. Выполнение безопасного запроса с соединением. Теперь, когда маски полностью сформированы, мы передаем их в запрос и выполняем соединение без каких-либо операций конкатенации внутри самого запроса.


Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТаблицаФильтра", ТаблицаФильтра);

Запрос.Текст = 
"ВЫБРАТЬ
|	ВТ_Фильтр.Маска КАК Маска
|ПОМЕСТИТЬ ВТ_Фильтр
|ИЗ
|	&ТаблицаФильтра КАК ВТ_Фильтр
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	Товары.Ссылка КАК Ссылка,
|	Товары.Код КАК Код
|ИЗ
|	Справочник.Номенклатура КАК Товары
|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК ВТ_Фильтр
|		ПО Товары.Код ПОДОБНО ВТ_Фильтр.Маска";

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

Данный вариант полностью легитимен, безопасен и успешно выполнится на любой поддерживаемой платформой 1С СУБД.

---

Критический нюанс безопасности: Работа со спецсимволами (Экранирование)

Если шаблоны для поиска вводятся пользователями вручную или загружаются из внешних неконтролируемых файлов, возникает серьезная проблема. Символы % (любое количество символов), _ (один произвольный символ), а также квадратные скобки [ ] и знак отрицания ^ являются управляющими символами для оператора ПОДОБНО.

Если пользователь введет код, содержащий, например, квадратную скобку, то СУБД PostgreSQL или Oracle выдаст аппаратную ошибку разбора регулярного выражения (invalid regular expression), и выполнение программы будет аварийно завершено. Если в коде окажется случайный символ процента, поиск вернет некорректные избыточные данные.

Выясним, как обезопасить наше решение:

  1. Использование Библиотеки стандартных подсистем (БСП): Если ваша конфигурация разработана с использованием БСП, настоятельно рекомендуется обрабатывать каждую входящую строку перед добавлением в таблицу параметров с помощью функции: ОбщегоНазначения.СформироватьСтрокуДляПоискаВЗапросе(СтрокаПоиска). Эта функция автоматически экранирует все служебные символы.
  2. Использование ключевого слова СПЕЦСИМВОЛ: В запросе необходимо явно указать экранирующий символ. Традиционно для этого используется символ тильды "~".

Пример безопасного составления шаблона во встроенном языке:


// Экранируем спецсимволы вручную, если не используется БСП
ЭкранированнаяСтрока = СтрокаПоиска;
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "~", "~~");
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "%", "~%");
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "_", "~_");
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "[", "~[");
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "]", "~]");

// Формируем итоговую маску (например, поиск по началу строки)
МаскаДляЗапроса = ЭкранированнаяСтрока + "%";

В тексте запроса условие соединения примет следующий вид:


|		ПО Товары.Код ПОДОБНО ВТ_Фильтр.Маска СПЕЦСИМВОЛ ""~""

---

Анализ производительности: Почему ПОДОБНО в соединениях — это угроза для СУБД?

Использование оператора ПОДОБНО в условии связи таблиц (JOIN ... ON ... LIKE ...) относится к числу наиболее «тяжелых» операций в реляционных базах данных. Проанализируем, почему это происходит.

При классическом соединении таблиц по строгому равенству (=) планировщик СУБД строит эффективный план выполнения, используя индексы (операции Index Seek или высокоэффективные алгоритмы слияния Merge Join / Hash Match). Но когда связь идет по условию ПОДОБНО, СУБД вынуждена использовать алгоритм Nested Loops (вложенные циклы).

Это означает, что для каждой строки из основной таблицы СУБД будет вынуждена произвести сканирование временной таблицы масок, выполняя текстовое сопоставление. Ситуация усугубляется, если маска начинается со спецсимвола процента (например, "%001"). В этом случае СУБД физически не может применить индекс к основной таблице и выполняет её полный просмотр (Table Scan / Index Scan).

Если в вашей целевой таблице содержится более 100 000 записей, а во временную таблицу масок передано более 50-100 элементов, такой запрос гарантированно вызовет:

Альтернативные архитектурные решения

Если вы понимаете, что производительность операции падает, мы рекомендуем отказаться от оператора ПОДОБНО в пользу более производительных проектных решений:

---

Резюме и рекомендации

Подведем итоги нашего разбора:

← На главную