При разработке на платформе «1С:Предприятие 8» программисты часто сталкиваются с необходимостью отбора данных по частичному совпадению. Если критерий поиска один, задача решается классическим использованием оператора ПОДОБНО в связке с параметром-шаблоном. Однако ситуация существенно усложняется, когда на входе у нас имеется целый массив неполных кодов или наименований, и нам нужно найти все записи, которые соответствуют хотя бы одному из шаблонов в этом массиве.
В этой статье мы подробно разберем, как решить эту задачу в запросах 1С, проанализируем основные подходы, их преимущества, скрытые угрозы безопасности и критические проблемы производительности СУБД. Мы также изучим официальные стандарты разработки фирмы «1С», которые уберегут ваш код от ошибок.
Рассмотрим по шагам два ключевых метода реализации данной задачи:
ИЛИ.ПОДОБНО (с учетом требований стандартов 1С).---
Этот способ основан на генерации текста запроса во встроенном языке 1С непосредственно перед его выполнением. Вместо того чтобы пытаться передать массив в один параметр, мы программно обходим исходный массив шаблонов и для каждого элемента дописываем в секцию ГДЕ дополнительное условие через логическое ИЛИ.
Проанализируем пример кода, реализующий этот подход:
// Исходный массив неполных кодов
МассивШаблонов = Новый Массив;
МассивШаблонов.Добавить("00-001%");
МассивШаблонов.Добавить("00-005%");
МассивШаблонов.Добавить("00-012%");
// Базовый текст запроса с плейсхолдером для динамического условия
ТекстЗапроса =
"ВЫБРАТЬ
| Товары.Ссылка КАК Ссылка,
| Товары.Код КАК Код
|ИЗ
| Справочник.Номенклатура КАК Товары
|ГДЕ
| &ДинамическоеУсловие";
ТекстУсловия = "";
ПараметрыЗапроса = Новый Структура;
// Программная сборка условий
Для Индекс = 0 По МассивШаблонов.ВГРАНИЦА() Цикл
ИмяПараметра = "Шаблон_" + Формат(Индекс, "ЧГ=0");
СтрокаШаблона = МассивШаблонов[Индекс];
// Формируем динамическое имя параметра для защиты от SQL-инъекций
Если ПустаяСтрока(ТекстУсловия) Тогда
ТекстУсловия = "Товары.Код ПОДОБНО &" + ИмяПараметра;
Иначе
ТекстУсловия = ТекстУсловия + " ИЛИ Товары.Код ПОДОБНО &" + ИмяПараметра;
EndЕсли;
ПараметрыЗапроса.Вставить(ИмяПараметра, СтрокаШаблона);
КонецЦикла;
// Если массив оказался пуст, подставим заведомо ложное условие
Если ПустаяСтрока(ТекстУсловия) Тогда
ТекстУсловия = "ЛОЖЬ";
КонецЕсли;
// Подставляем сформированное условие в текст запроса
ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "&ДинамическоеУсловие", ТекстУсловия);
// Инициализация и выполнение запроса
Запрос = Новый Запрос(ТекстЗапроса);
Для Каждого КлючИЗначение Из ПараметрыЗапроса Цикл
Запрос.УстановитьПараметр(КлючИЗначение.Ключ, КлючИЗначение.Значение);
КонецЦикла;
РезультатЗапроса = Запрос.Выполнить();
Преимущества динамического способа:
"00-001%"), СУБД может использовать индекс по полю Код для быстрого поиска (операция Index Seek или Index Scan по диапазону).Недостатки динамического способа:
---
Второй подход предлагает передать весь набор искомых шаблонов в запрос в виде единой таблицы значений, поместить её во временную таблицу и выполнить левое или внутреннее соединение с целевой таблицей.
Однако при реализации этого способа разработчики часто совершают грубую ошибку, нарушающую официальные стандарты разработки 1С.
Рассмотрим ошибочный вариант запроса, который часто предлагают на форумах:
// ТАК ДЕЛАТЬ НЕЛЬЗЯ!
ВЫБРАТЬ ВТ_Шаблоны.Код КАК Маска
ПОМЕСТИТЬ ВТ_Фильтр
ИЗ &ТаблицаШаблонов КАК ВТ_Шаблоны;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ Товары.Ссылка
ИЗ Справочник.Номенклатура КАК Товары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК ВТ_Фильтр
ПО Товары.Код ПОДОБНО "%" + ВТ_Фильтр.Маска
Согласно требованиям стандарта 1С «Особенности использования в запросах оператора ПОДОБНО», правым операндом этого оператора может выступать исключительно строковая константа (например, "001%") либо один параметр запроса (например, &Маска).
Запрещено конструировать маску подобия внутри запроса путем конкатенации строк, сложения полей разных таблиц или вычислений. Такое поведение не только нарушает стандарты и вызывает предупреждения в автоматических системах проверки кода (1C:EDT, SonarQube), но и приводит к фатальным синтаксическим ошибкам при работе базы данных на СУБД IBM DB2, Oracle или устаревших версиях MS SQL Server.
Чтобы обойти это ограничение и написать код, полностью соответствующий стандартам 1С, мы должны подготовить строки масок на стороне встроенного языка еще до передачи таблицы параметров в запрос.
Разберем правильный алгоритм работы по шагам:
Шаг 1. Создание и подготовка таблицы значений. Мы создаем таблицу значений с типизированной колонкой для масок поиска и заполняем её строками, уже содержащими спецсимволы маскирования (символ %).
// Создаем таблицу значений с типизацией колонок
ТаблицаФильтра = Новый ТаблицаЗначений;
ОписаниеТиповСтрока = Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(50));
ТаблицаФильтра.Колонки.Добавить("Маска", ОписаниеТиповСтрока);
// Заполняем таблицу, добавляя спецсимволы "%" на стороне встроенного языка
Для Каждого ЗначениеИзМассива Из МассивНеполныхКодов Цикл
НоваяСтрока = ТаблицаФильтра.Добавить();
// Например, мы ищем коды, которые НАЧИНАЮТСЯ со значений из массива
НоваяСтрока.Маска = СокрЛП(ЗначениеИзМассива) + "%";
КонецЦикла;
Шаг 2. Выполнение безопасного запроса с соединением. Теперь, когда маски полностью сформированы, мы передаем их в запрос и выполняем соединение без каких-либо операций конкатенации внутри самого запроса.
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТаблицаФильтра", ТаблицаФильтра);
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_Фильтр.Маска КАК Маска
|ПОМЕСТИТЬ ВТ_Фильтр
|ИЗ
| &ТаблицаФильтра КАК ВТ_Фильтр
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Товары.Ссылка КАК Ссылка,
| Товары.Код КАК Код
|ИЗ
| Справочник.Номенклатура КАК Товары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Фильтр КАК ВТ_Фильтр
| ПО Товары.Код ПОДОБНО ВТ_Фильтр.Маска";
РезультатЗапроса = Запрос.Выполнить();
Данный вариант полностью легитимен, безопасен и успешно выполнится на любой поддерживаемой платформой 1С СУБД.
---
Если шаблоны для поиска вводятся пользователями вручную или загружаются из внешних неконтролируемых файлов, возникает серьезная проблема. Символы % (любое количество символов), _ (один произвольный символ), а также квадратные скобки [ ] и знак отрицания ^ являются управляющими символами для оператора ПОДОБНО.
Если пользователь введет код, содержащий, например, квадратную скобку, то СУБД PostgreSQL или Oracle выдаст аппаратную ошибку разбора регулярного выражения (invalid regular expression), и выполнение программы будет аварийно завершено. Если в коде окажется случайный символ процента, поиск вернет некорректные избыточные данные.
Выясним, как обезопасить наше решение:
ОбщегоНазначения.СформироватьСтрокуДляПоискаВЗапросе(СтрокаПоиска). Эта функция автоматически экранирует все служебные символы."~".Пример безопасного составления шаблона во встроенном языке:
// Экранируем спецсимволы вручную, если не используется БСП
ЭкранированнаяСтрока = СтрокаПоиска;
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "~", "~~");
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "%", "~%");
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "_", "~_");
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "[", "~[");
ЭкранированнаяСтрока = СтрЗаменить(ЭкранированнаяСтрока, "]", "~]");
// Формируем итоговую маску (например, поиск по началу строки)
МаскаДляЗапроса = ЭкранированнаяСтрока + "%";
В тексте запроса условие соединения примет следующий вид:
| ПО Товары.Код ПОДОБНО ВТ_Фильтр.Маска СПЕЦСИМВОЛ ""~""
---
Использование оператора ПОДОБНО в условии связи таблиц (JOIN ... ON ... LIKE ...) относится к числу наиболее «тяжелых» операций в реляционных базах данных. Проанализируем, почему это происходит.
При классическом соединении таблиц по строгому равенству (=) планировщик СУБД строит эффективный план выполнения, используя индексы (операции Index Seek или высокоэффективные алгоритмы слияния Merge Join / Hash Match). Но когда связь идет по условию ПОДОБНО, СУБД вынуждена использовать алгоритм Nested Loops (вложенные циклы).
Это означает, что для каждой строки из основной таблицы СУБД будет вынуждена произвести сканирование временной таблицы масок, выполняя текстовое сопоставление. Ситуация усугубляется, если маска начинается со спецсимвола процента (например, "%001"). В этом случае СУБД физически не может применить индекс к основной таблице и выполняет её полный просмотр (Table Scan / Index Scan).
Если в вашей целевой таблице содержится более 100 000 записей, а во временную таблицу масок передано более 50-100 элементов, такой запрос гарантированно вызовет:
Если вы понимаете, что производительность операции падает, мы рекомендуем отказаться от оператора ПОДОБНО в пользу более производительных проектных решений:
ХвостоваяЧастьКода или ПрефиксКода). Заполняйте этот реквизит программно в обработчике события ПередЗаписью объекта. В запросе вы сможете использовать строгое равенство (=), что позволит СУБД мгновенно находить строки по индексу.ПОДОБНО неизбежно, жестко ограничьте размер таблицы шаблонов. Обрабатывайте исходный массив порциями (например, не более 50 элементов за один запрос) непосредственно в цикле во встроенном языке. Это позволит удержать нагрузку на СУБД в допустимых границах.---
Подведем итоги нашего разбора:
ИЛИ. Он прост, надежен и позволяет СУБД эффективно использовать существующие индексы.% обязательно должны подготавливаться во встроенном языке 1С до передачи таблицы в запрос, чтобы не нарушать стандарты разработки 1С.СПЕЦСИМВОЛ для предотвращения падений системы.ПОДОБНО, отдавая предпочтение денормализации данных и поиску по строгому равенству.