Как реализовать регистронезависимый поиск по строковым колонкам в таблице значений

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

При активной разработке на платформе 1С:Предприятие часто возникает задача быстрого поиска строк в оперативной памяти. Когда мы работаем с коллекцией ТаблицаЗначений, штатные методы поиска строк ведут себя строго с учетом регистра символов. Разберем особенности платформенных механизмов и рассмотрим эффективные варианты реализации поиска без учета регистра.

Почему штатные методы таблицы значений чувствительны к регистру

Проанализируем базовое поведение встроенного языка 1С. Методы ТаблицаЗначений.Найти() и ТаблицаЗначений.НайтиСтроки() выполняют точное бинарное сравнение значений в оперативной памяти. Если строка в таблице содержит значение "Иванов", а поиск выполняется по значению "иванов", совпадение найдено не будет.

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

Рассмотрим основные способы обхода этого ограничения в зависимости от размера таблицы, частоты поиска и требований к производительности.

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

Этот способ является классическим и наиболее производительным, если поиск по таблице выполняется многократно, а структуру таблицы можно дополнять новыми колонками.

Разберем алгоритм по шагам:

  1. Добавляем в таблицу значений служебную строковую колонку, например НаименованиеДляПоиска.
  2. При заполнении или модификации строк таблицы записываем в эту колонку нормализованное значение с помощью функций НРег() или ВРег(), совмещая с СокрЛП().
  3. Добавляем индекс по служебной колонке с помощью ТаблицаЗначений.Индексы.Добавить().
  4. Выполняем поиск методом НайтиСтроки(), передавая искомое значение, также приведенное к единому регистру.

Посмотрим на практический пример реализации:


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

// Заполняем индекс для быстрого поиска через НайтиСтроки
ТаблицаТоваров.Индексы.Добавить("Наименование_Поиск");

// Пример заполнения строки
НоваяСтрока = ТаблицаТоваров.Добавить();
НоваяСтрока.Артикул = "ART-001";
НоваяСтрока.Наименование = "Шоколад Аленка";
НоваяСтрока.Наименование_Поиск = ВРег(СокрЛП(НоваяСтрока.Наименование));

// Выполняем регистронезависимый поиск
ИскомыйТекст = "шоколад аленка";
Отбор = Новый Структура;
Отбор.Вставить("Наименование_Поиск", ВРег(СокрЛП(ИскомыйТекст)));

НайденныеСтроки = ТаблицаТоваров.НайтиСтроки(Отбор);

Преимущество: Поиск выполняется максимально быстро за счет штатного индекса платформы.

Недостаток: Расходуется дополнительная оперативная память на хранение служебной колонки, требуется контроль актуальности данных при изменении основной колонки.

Способ 2. Обход таблицы с использованием функции СтрСравнить

Если поиск выполняется однократно или таблица содержит относительно небольшое количество строк (до нескольких тысяч), добавлять служебные колонки не имеет смысла. В этом случае эффективнее обойти строки таблицы циклом, используя специализированную функцию СтрСравнить().

Функция СтрСравнить(Строка1, Строка2) сравнивает строки без учета регистра и возвращает 0, если они равны. В отличие от выражений ВРег(Стр1) = ВРег(Стр2), эта функция не создает в памяти новые копии строк на каждой итерации цикла, что снижает нагрузку на сборщик мусора.

Рассмотрим пример обхода:


ИскомоеЗначение = "аленка";
НайденныеСтроки = Новый Массив;

Для Каждого СтрТаблицы Из ТаблицаТоваров Цикл
    Если СтрСравнить(СтрТаблицы.Наименование, ИскомоеЗначение) = 0 Тогда
        НайденныеСтроки.Добавить(СтрТаблицы);
    КонецЕсли;
КонецЦикла;

Способ 3. Внешнее индексирование через объект Соответствие

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

Посмотрим, как организовать такой поиск:


ИндексПоиска = Новый Соответствие;

// Заполняем внешний кэш строк таблицы
Для Каждого СтрТаблицы Из ТаблицаТоваров Цикл
    КлючПоиска = ВРег(СокрЛП(СтрТаблицы.Наименование));
    
    // Если ключи могут дублироваться, сохраняем массив строк
    СписокСтрок = ИндексПоиска[КлючПоиска];
    Если СписокСтрок = Неопределено Тогда
        СписокСтрок = Новый Массив;
        ИндексПоиска.Вставить(КлючПоиска, СписокСтрок);
    КонецЕсли;
    СписокСтрок.Добавить(СтрТаблицы);
КонецЦикла;

// Быстрый поиск за O(1)
ИскомыйКлюч = ВРег(СокрЛП("шоколад аленка"));
НайденныеСтроки = ИндексПоиска[ИскомыйКлюч];
Если НайденныеСтроки = Неопределено Тогда
    НайденныеСтроки = Новый Массив;
КонецЕсли;

Способ 4. Использование запроса к временной таблице

Механизм запросов 1С по умолчанию работает без учета регистра строк при использовании операторов сравнения и конструкции ПОДОБНО (в зависимости от настроек сопоставления СУБД). Однако передача таблицы значений во временную таблицу требует сериализации данных и обращения к СУБД.

Посмотрим на пример запроса:


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

Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
Запрос.УстановитьПараметр("ИскомыйТекст", "%" + ИскомаяПодстрока + "%");

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

Когда использовать запрос: Если данные уже находятся в СУБД или требуется сложная фильтрация, агрегация или соединение с другими таблицами базы данных.

Когда запрос не подходит: Если таблица значений часто меняется в коде, имеет небольшой размер или находится в контексте клиентского вызова. Постоянная передача таблицы значений на сервер баз данных приведет к избыточным накладным расходам.

Рекомендации по выбору метода

Подведем итоги и выделим оптимальный сценарий для каждого подхода:

  1. Для многократного точного поиска в большой таблице с возможностью менять колонки — используем служебную колонку с нормализованным текстом и индекс.
  2. Для поиска без изменения колонок исходной таблицы — строим внешний кэш в объекте Соответствие.
  3. Для разового поиска или небольших таблиц — используем простой цикл и функцию СтрСравнить().
  4. Для сложного поиска по маске подстроки в больших объемах данных — применяем запрос с временной таблицей или объект РегулярноеВыражение.
← На главную