При активной работе со справочником номенклатуры на складе или в оптово-розничной торговле операторы часто сталкиваются с проблемой: в наименовании товара производитель, модель, тип и характеристики записаны в случайной последовательности. Например, один товар может называться «asus eee pc ноутбук 1225b 4 кг», а другой — «ноутбук 3кг asus eee pc X101CH». В типовых механизмах платформы 1С 7.7 стандартный поиск привязан к жесткой последовательности символов или точному совпадению подстроки, из-за чего найти товар по нескольким ключевым словам становится крайне сложно.
Проанализируем технические и организационные способы решения этой задачи, начиная от создания внешних инструментов поиска и заканчивая оптимизацией структуры справочника.
Выясним причину, почему стандартная форма подбора не находит позицию по фрагментам слов. В типовой конфигурации «Торговля и Склад 7.7» поиск в списке справочника выполняется по началу строки либо по точному вхождению одной подстроки. Спецсимволы маскирования (например, знак %) штатным интерфейсом не обрабатываются и воспринимаются как обычные текстовые символы.
Для реализации гибкого поиска необходимо решить ключевую задачу: разбить введенную пользователем поисковую строку на отдельные слова и выполнить выборку элементов, в наименовании которых присутствуют все указанные слова одновременно (логическое условие «И»), независимо от их позиции и расстояния друг от друга.
Рассмотрим базовый алгоритм, который можно реализовать штатным встроенным языком 1С без использования внешних библиотек и компонентов. Суть метода заключается в разделении поискового шаблона на составляющие части с помощью разделителя (пробела или знака процента) и проверке вхождения каждой части в наименование текущего элемента справочника Справочник.Номенклатура.
Посмотрим на пример реализации алгоритма с последовательным отсечением строк:
тзНоменклатура = СоздатьОбъект("ТаблицаЗначений");
тзНоменклатура.НоваяКолонка("Номенклатура");
СтрШаблон = Врег(СокрЛП(ШаблонПоиска));
сзШаблон = глРазложить(СтрШаблон, " "); // разбиваем строку поиска на отдельные слова
СпрНоменклатура = СоздатьОбъект("Справочник.Номенклатура");
СпрНоменклатура.ВыбратьЭлементы();
Пока СпрНоменклатура.ПолучитьЭлемент() = 1 Цикл
Если СпрНоменклатура.ЭтоГруппа() = 1 Тогда
Продолжить;
КонецЕсли;
Наименование = Врег(СпрНоменклатура.Наименование);
ВсеНайдено = 0;
Для ии = 1 По сзШаблон.РазмерСписка() Цикл
ТекущееСлово = СокрЛП(сзШаблон.ПолучитьЗначение(ии));
Если ПустаяСтрока(ТекущееСлово) = 1 Тогда
Продолжить;
КонецЕсли;
Если Найти(Наименование, ТекущееСлово) > 0 Тогда
ВсеНайдено = ВсеНайдено + 1;
Иначе
Прервать; // слово не найдено, элемент не подходит
КонецЕсли;
КонецЦикла;
// Если количество совпадений равно количеству слов в запросе
Если ВсеНайдено = сзШаблон.РазмерСписка() Тогда
тзНоменклатура.НоваяСтрока();
тзНоменклатура.Номенклатура = СпрНоменклатура.ТекущийЭлемент();
КонецЕсли;
КонецЦикла;
Такой подход универсален, работает в любых базах (как DBF, так и SQL) и не требует внешних драйверов, однако при объеме справочника свыше нескольких десятков тысяч позиций скорость полного перебора может снижаться.
Разберем еще один штатный вариант, использующий объект Запрос встроенного языка. В этом случае текст запроса динамически конструируется в цикле, добавляя секцию Условие для каждого фрагмента текста.
Посмотрим на программный код построения такого запроса:
Запрос = СоздатьОбъект("Запрос");
РазбиваемаяСтрока = СтрЗаменить(СокрЛП(ПодСтрокаПоиска), " ", РазделительСтрок);
ТекстЗапроса = "
|Без Итогов;
|ТекущийЭлемент = Справочник.Номенклатура.ТекущийЭлемент;
|Наименование = Справочник.Номенклатура.Наименование;
|Группировка ТекущийЭлемент Без Упорядочивания Без Групп;";
Для Индекс = 1 По СтрКоличествоСтрок(РазбиваемаяСтрока) Цикл
Слово = ВРег(СтрПолучитьСтроку(РазбиваемаяСтрока, Индекс));
Если ПустоеЗначение(Слово) = 1 Тогда
Продолжить;
КонецЕсли;
ТекстЗапроса = ТекстЗапроса + "
|Условие(Найти(ВРег(Наименование), """ + Слово + """) > 0);";
КонецЦикла;
Если Запрос.Выполнить(ТекстЗапроса) <> 0 Тогда
Пока Запрос.Группировка(1) = 1 Цикл
// Обработка найденных элементов
НайденныйТовар = Запрос.ТекущийЭлемент;
КонецЦикла;
КонецЕсли;
Когда требуется мгновенный отклик на базах большого объема, оптимальным решением является использование внешних компонент прямых запросов (например, 1C++ или 1CSQLite). В этом случае запрос отбирает данные на уровне базы через оператор LIKE для каждого слова.
Разберем по шагам построение запроса:
РазбиваемаяСтрока = СтрЗаменить(СокрЛП(стрПоиска), " ", РазделительСтрок);
текст = "SELECT id [Товар :Справочник.Номенклатура]
|FROM $Справочник.Номенклатура
|WHERE isfolder = 2 AND ismark <> '*'";
Для Индекс = 1 По СтрКоличествоСтрок(РазбиваемаяСтрока) Цикл
Слово = СтрПолучитьСтроку(РазбиваемаяСтрока, Индекс);
Если ПустоеЗначение(Слово) = 0 Тогда
текст = текст + " AND (descr LIKE '%" + Слово + "%')";
КонецЕсли;
КонецЦикла;
тзРезультат = ЗапросSQL.ВыполнитьЗапрос(текст);
Прямой запрос фильтрует записи на уровне ядра СУБД, что обеспечивает результат за доли секунды даже на базах в сотни тысяч строк.
Если у пользователя нет прав на редактирование конфигурации или внесение изменений в типовую структуру запрещено регламентом компании, функционал реализуется через внешнюю обработку (файл с расширением .ert).
Помимо программной доработки подбора, существенно ускорить поиск товаров оператором помогают правильная организация данных и использование стандартных реквизитов: