Как программно реализовать поиск по справочнику аналогично штатному механизму 1С?

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

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

Представим типичную ситуацию: пользователь вводит поисковый запрос КЭ 04-05-2018/2, рассчитывая найти договор, который в базе данных записан как Дог. № КЭ 04-05-2018. Обычный запрос с конструкцией ПОДОБНО вернет пустой результат, так как подстрока "/2" отсутствует в наименовании договора. При этом штатный интерактивный поиск в форме списка успешно находит этот элемент. Выясним причину такого поведения и разберем по шагам, как воспроизвести логику умного поиска программными методами.

Почему простой запрос ПОДОБНО не работает так же, как штатный поиск?

Для того чтобы понять разницу, проанализируем ситуацию изнутри. Штатный механизм поиска платформы 1С работает по комбинированному принципу:

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

Рассмотрим подробнее три основных способа программной реализации такого "умного" поиска.

Решение 1. Программное использование Полнотекстового Поиска Данных (ППД)

Это наиболее правильный и близкий к штатному поведению вариант. Платформа предоставляет объект ПолнотекстовыйПоиск, с помощью которого можно обращаться к поисковому индексу базы данных. Посмотрим на практический пример реализации такого поиска:


// Определяем строку поиска, полученную от пользователя
СтрокаПоиска = "КЭ 04-05-2018/2";

// Создаем список полнотекстового поиска с ограничением на количество результатов (например, 50 элементов)
СписокПоиска = ПолнотекстовыйПоиск.СоздатьСписок(СтрокаПоиска, 50);

// КРИТИЧЕСКИ ВАЖНО: Всегда ограничивайте область поиска метаданными.
// Если этого не сделать, система будет искать по всей базе данных (справочники, документы, регистры),
// что приведет к колоссальным нагрузкам на сервер СУБД.
ОбластьПоиска = Новый Массив;
ОбластьПоиска.Добавить(Метаданные.Справочники.ДоговорыКонтрагентов);
СписокПоиска.ОбластьПоиска = ОбластьПоиска;

// Настраиваем порог нечеткости (значение от 0 до 100).
// Небольшой процент позволяет игнорировать мелкие опечатки пользователей.
СписокПоиска.ПорогНечеткости = 15; 

// Запускаем процесс поиска первого блока данных
СписокПоиска.ПерваяЧасть();

// Анализируем результаты поиска
Если СписокПоиска.Количество() > 0 Тогда
    Для Каждого СтрокаРезультата Из СписокПоиска Цикл
        // Свойство "Значение" содержит прямую ссылку на найденный объект СправочникСсылка
        НайденныйДоговор = СтрокаРезультата.Значение;
        Сообщить("Найден объект: " + НайденныйДоговор + " (Представление: " + СтрокаРезультата.Представление + ")");
    КонецЦикла;
Иначе
    Сообщить("Полнотекстовый поиск не дал результатов.");
КонецЕсли;

При использовании ППД важно учитывать состояние его индекса. Если индекс устарел или отключен, поиск может вернуть неполные или пустые результаты. Проверить актуальность индекса можно следующим образом:


Если Не ПолнотекстовыйПоиск.ИндексАктуален() Тогда
    // Индекс устарел. Обратите внимание: метод ПолнотекстовыйПоиск.ОбновитьИндекс() 
    // не рекомендуется вызывать непосредственно в кодовой сессии пользователя на больших базах,
    // так как это может заблокировать интерфейс. Данную операцию лучше доверять регламентным заданиям.
    Сообщить("Внимание! Полнотекстовый индекс не актуален. Результаты поиска могут быть неточными.");
КонецЕсли;

Решение 2. Динамическое построение SQL-запроса по фрагментам (БСП-стиль)

Если полнотекстовый поиск в вашей информационной базе отключен или вы хотите гарантировать точное совпадение фрагментов без использования сторонних индексов СУБД, отличным решением станет имитация логики платформы через построение динамического запроса. Для этого разберем поисковый запрос на массив отдельных слов, очистим их и свяжем в запросе через логическое И.

Для реализации этого подхода воспользуемся стандартными функциями Библиотеки стандартных подсистем (БСП). Рассмотрим этот алгоритм по шагам:


// Входная строка поиска
СтрокаПоиска = "КЭ 04-05-2018/2";

// 1. Разбиваем строку поиска на отдельные слова с помощью стандартной функции БСП.
// Функция автоматически убирает лишние пробелы и возвращает массив уникальных слов.
МассивСлов = СтроковыеФункцииКлиентСервер.РазложитьСтрокуПоискаВМассивСлов(СтрокаПоиска);

Если МассивСлов.Количество() = 0 Тогда
    Сообщить("Поисковый запрос пуст.");
    Возврат;
КонецЕсли;

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

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

// 3. Обходим каждое слово в цикле и формируем свое условие ПОДОБНО для каждого слова
Для Индекс = 0 По МассивСлов.Количество() - 1 Цикл
    ТекущееСлово = МассивСлов[Индекс];
    
    // Экранируем спецсимволы СУБД (такие как %, _, [), чтобы они воспринимались как обычные символы
    ЭкранированноеСлово = ОбщегоНазначения.СформироватьСтрокуДляПоискаВЗапросе(ТекущееСлово);
    
    ИмяПараметра = "ПараметрПоиска_" + Индекс;
    ПараметрыЗапроса.Вставить(ИмяПараметра, "%" + ЭкранированноеСлово + "%");
    
    // Добавляем условие поиска по наименованию (при необходимости можно искать и по другим реквизитам)
    УсловияЗапроса.Добавить("Договоры.Наименование ПОДОБНО &" + ИмяПараметра);
КонецЦикла;

// 4. Объединяем все условия через оператор И.
// Это гарантирует, что элемент будет найден, если в его наименовании содержатся ВСЕ слова из запроса
ТекстЗапроса = ТекстЗапроса + СтрСоединить(УсловияЗапроса, " И ");

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

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

Пока Выборка.Следующий() Цикл
    Сообщить("Найден подходящий договор: " + Выборка.Договор + " (" + Выборка.Наименование + ")");
КонецЦикла;

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

Важный нюанс: Экранирование спецсимволов при поиске через ПОДОБНО

Разберем подробнее, зачем нужно экранирование символов и как его реализовать самостоятельно, если в вашей конфигурации не используется БСП. Символы процента (%) и подчеркивания (_) являются управляющими в языке запросов 1С. Процент означает любую последовательность произвольных символов, а подчеркивание — ровно один любой символ.

Если пользователь введет поисковый запрос вроде "Договор_1", СУБД без экранирования найдет и "Договор-1", и "Договор_1", и "Договор/1". Чтобы избежать ложных срабатываний, нужно использовать конструкцию СПЕЦСИМВОЛ в запросе. Посмотрим, как это выглядит в ручной программной реализации:


// Исходная строка от пользователя
ИсходноеСлово = "Договор_1%";

// Заменяем спецсимволы, добавляя перед ними экранирующий символ, например "\"
ЭкранированноеСлово = СтрЗаменить(ИсходноеСлово, "\", "\\");
ЭкранированноеСлово = СтрЗаменить(ЭкранированноеСлово, "%", "\%");
ЭкранированноеСлово = СтрЗаменить(ЭкранированноеСлово, "_", "\_");

// Формируем текст запроса с указанием спецсимвола
ТекстЗапроса = 
"ВЫБРАТЬ
|	Договоры.Ссылка
|ИЗ
|	Справочник.ДоговорыКонтрагентов КАК Договоры
|ГДЕ
|	Договоры.Наименование ПОДОБНО &ПараметрПоиска СПЕЦСИМВОЛ ""\""";

Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("ПараметрПоиска", "%" + ЭкранированноеСлово + "%");

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

Решение 3. Тонкая настройка автоподбора в управляемых формах

Очень часто задача "умного" поиска возникает при вводе данных пользователем непосредственно в текстовое поле выбора на форме (так называемый автоподбор). Вместо того чтобы вручную переписывать логику поиска в модулях менеджеров, мы можем использовать встроенные возможности платформы и управлять ими программно в событии АвтоПодбор элемента формы.

Посмотрим, как настроить поведение автоподбора так, чтобы оно искало фрагменты в любой части наименования с привлечением полнотекстового индекса:


&НаКлиенте
Процедура ДоговорАвтоПодбор(Элемент, Текст, ДанныеВыбора, ПараметрыПолученияДанных, Ожидание, СтандартнаяОбработка)
    
    // 1. Отменяем стандартную обработку, если планируем полностью переопределить состав выдачи,
    // либо оставляем ее Истиной, но корректируем параметры поиска.
    // В данном случае мы просто скорректируем параметры поведения платформы:
    
    // Настраиваем поиск по любой части строки (по умолчанию платформа может искать только по началу строки)
    ПараметрыПолученияДанных.СпособПоискаСтроки = СпособПоискаСтрокиПриВводеПоСтроке.ЛюбаяЧасть;
    
    // Явно указываем платформе использовать полнотекстовый поиск для подбора элементов
    ПараметрыПолученияДанных.ПолнотекстовыйПоиск = ПолнотекстовыйПоискПриВводеПоСтроке.Использовать;
    
КонецПроцедуры

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

Сравнение методов и рекомендации

Для выбора оптимального инструмента в ваших разработках рекомендуем руководствоваться следующими правилами:

← На главную