При активной разработке на платформе 1С:Предприятие часто возникает задача быстрого поиска строк в оперативной памяти. Когда мы работаем с коллекцией ТаблицаЗначений, штатные методы поиска строк ведут себя строго с учетом регистра символов. Разберем особенности платформенных механизмов и рассмотрим эффективные варианты реализации поиска без учета регистра.
Проанализируем базовое поведение встроенного языка 1С. Методы ТаблицаЗначений.Найти() и ТаблицаЗначений.НайтиСтроки() выполняют точное бинарное сравнение значений в оперативной памяти. Если строка в таблице содержит значение "Иванов", а поиск выполняется по значению "иванов", совпадение найдено не будет.
Важно помнить, что даже добавление индексов через свойство ТаблицаЗначений.Индексы не решает эту проблему. Штатный индекс таблицы значений строится с учетом регистра исходных данных, поэтому он ускоряет только поиск в точном соответствии с регистром символов.
Рассмотрим основные способы обхода этого ограничения в зависимости от размера таблицы, частоты поиска и требований к производительности.
Этот способ является классическим и наиболее производительным, если поиск по таблице выполняется многократно, а структуру таблицы можно дополнять новыми колонками.
Разберем алгоритм по шагам:
НаименованиеДляПоиска.НРег() или ВРег(), совмещая с СокрЛП().ТаблицаЗначений.Индексы.Добавить().НайтиСтроки(), передавая искомое значение, также приведенное к единому регистру.Посмотрим на практический пример реализации:
// Создаем структуру таблицы и добавляем служебную колонку
ТаблицаТоваров = Новый ТаблицаЗначений;
ТаблицаТоваров.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(50)));
ТаблицаТоваров.Колонки.Добавить("Наименование", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(150)));
ТаблицаТоваров.Колонки.Добавить("Наименование_Поиск", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(150)));
// Заполняем индекс для быстрого поиска через НайтиСтроки
ТаблицаТоваров.Индексы.Добавить("Наименование_Поиск");
// Пример заполнения строки
НоваяСтрока = ТаблицаТоваров.Добавить();
НоваяСтрока.Артикул = "ART-001";
НоваяСтрока.Наименование = "Шоколад Аленка";
НоваяСтрока.Наименование_Поиск = ВРег(СокрЛП(НоваяСтрока.Наименование));
// Выполняем регистронезависимый поиск
ИскомыйТекст = "шоколад аленка";
Отбор = Новый Структура;
Отбор.Вставить("Наименование_Поиск", ВРег(СокрЛП(ИскомыйТекст)));
НайденныеСтроки = ТаблицаТоваров.НайтиСтроки(Отбор);
Преимущество: Поиск выполняется максимально быстро за счет штатного индекса платформы.
Недостаток: Расходуется дополнительная оперативная память на хранение служебной колонки, требуется контроль актуальности данных при изменении основной колонки.
Если поиск выполняется однократно или таблица содержит относительно небольшое количество строк (до нескольких тысяч), добавлять служебные колонки не имеет смысла. В этом случае эффективнее обойти строки таблицы циклом, используя специализированную функцию СтрСравнить().
Функция СтрСравнить(Строка1, Строка2) сравнивает строки без учета регистра и возвращает 0, если они равны. В отличие от выражений ВРег(Стр1) = ВРег(Стр2), эта функция не создает в памяти новые копии строк на каждой итерации цикла, что снижает нагрузку на сборщик мусора.
Рассмотрим пример обхода:
ИскомоеЗначение = "аленка";
НайденныеСтроки = Новый Массив;
Для Каждого СтрТаблицы Из ТаблицаТоваров Цикл
Если СтрСравнить(СтрТаблицы.Наименование, ИскомоеЗначение) = 0 Тогда
НайденныеСтроки.Добавить(СтрТаблицы);
КонецЕсли;
КонецЦикла;
Бывают ситуации, когда структуру исходной таблицы значений изменять нельзя, но поиск по ней требуется делать регулярно. В этом случае мы можем построить внешний индекс в памяти с помощью объекта Соответствие.
Посмотрим, как организовать такой поиск:
ИндексПоиска = Новый Соответствие;
// Заполняем внешний кэш строк таблицы
Для Каждого СтрТаблицы Из ТаблицаТоваров Цикл
КлючПоиска = ВРег(СокрЛП(СтрТаблицы.Наименование));
// Если ключи могут дублироваться, сохраняем массив строк
СписокСтрок = ИндексПоиска[КлючПоиска];
Если СписокСтрок = Неопределено Тогда
СписокСтрок = Новый Массив;
ИндексПоиска.Вставить(КлючПоиска, СписокСтрок);
КонецЕсли;
СписокСтрок.Добавить(СтрТаблицы);
КонецЦикла;
// Быстрый поиск за O(1)
ИскомыйКлюч = ВРег(СокрЛП("шоколад аленка"));
НайденныеСтроки = ИндексПоиска[ИскомыйКлюч];
Если НайденныеСтроки = Неопределено Тогда
НайденныеСтроки = Новый Массив;
КонецЕсли;
Механизм запросов 1С по умолчанию работает без учета регистра строк при использовании операторов сравнения и конструкции ПОДОБНО (в зависимости от настроек сопоставления СУБД). Однако передача таблицы значений во временную таблицу требует сериализации данных и обращения к СУБД.
Посмотрим на пример запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Артикул КАК Артикул,
| ТЗ.Наименование КАК Наименование
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТаблицаТоваров КАК ТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Артикул КАК Артикул,
| ВТ.Наименование КАК Наименование
|ИЗ
| ВТ_Данные КАК ВТ
|ГДЕ
| ВТ.Наименование ПОДОБНО &ИскомыйТекст";
Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
Запрос.УстановитьПараметр("ИскомыйТекст", "%" + ИскомаяПодстрока + "%");
Результат = Запрос.Выполнить().Выгрузить();
Когда использовать запрос: Если данные уже находятся в СУБД или требуется сложная фильтрация, агрегация или соединение с другими таблицами базы данных.
Когда запрос не подходит: Если таблица значений часто меняется в коде, имеет небольшой размер или находится в контексте клиентского вызова. Постоянная передача таблицы значений на сервер баз данных приведет к избыточным накладным расходам.
Подведем итоги и выделим оптимальный сценарий для каждого подхода:
РегулярноеВыражение.