Разберем типичную практическую задачу при интеграциях (например, с телефонией): в конфигурации уже сформирована таблица с контрагентами и их контактными данными, и по входящему номеру требуется быстро найти владельца контакта. Номера телефонов в базе данных почти всегда записаны произвольно: с кодом +7, 8, без кода страны, со скобками, дефисами или пробелами. Рассмотрим ограничения встроенных функций платформы и разберем эффективные подходы к решению этой задачи.
Попытка использовать строковые функции или стандартные методы поиска в таблице сталкивается со следующими особенностями платформы:
ТаблицаЗначений.НайтиСтроки() принимает структуру отбора и ищет строго по точному совпадению. Использовать регулярные выражения, спецсимволы или маски подстановки вроде * или % в нем невозможно.ПОДОБНО "%926..." действительно позволяет найти совпадение по маске, но символ процента в начале условия отключает использование индексов на уровне СУБД, вызывая полное сканирование таблицы. При частых вызовах в высоконагруженных сценариях это создает высокую нагрузку на систему.Если таблица уже выгружена в память и необходимо быстро найти строку по маске, можно выполнить запрос непосредственно к существующей ТаблицеЗначений, передав ее как параметр. Посмотрим на реализацию:
// Подготовим маску поиска: убираем префиксы и дефисы
// Для поиска номера "9261234567" формируем шаблон с процентами
МаскаПоиска = "%926%123%45%67%";
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Контрагент КАК Контрагент,
| ТЗ.НомерТелефона КАК НомерТелефона
|ПОМЕСТИТЬ ВТДанные
|ИЗ
| &ТаблицаКонтактов КАК ТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Контрагент КАК Контрагент,
| ВТ.НомерТелефона КАК НомерТелефона
|ИЗ
| ВТДанные КАК ВТ
|ГДЕ
| ВТ.НомерТелефона ПОДОБНО &МаскаПоиска";
Запрос.УстановитьПараметр("ТаблицаКонтактов", ТаблицаКонтактов);
Запрос.УстановитьПараметр("МаскаПоиска", МаскаПоиска);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Этот вариант подходит для разовых операций, однако при интенсивном потоке вызовов создание объекта Запрос и сериализация таблицы в памяти создают накладные расходы.
Для достижения максимального быстродействия правильнее отказаться от поиска по маске в пользу точного совпадения. Для этого приведем номера к каноническому виду: оставим только значащие цифры и отсечем телефонный префикс страны. Как правило, последние 10 цифр телефонного номера уникально определяют абонента.
Разберем вспомогательную функцию для очистки номера от лишних символов:
Функция ПолучитьТолькоЦифры(Знач ВходнаяСтрока)
Результат = "";
ДопустимыеСимволы = "0123456789";
Для Индекс = 1 По СтрДлина(ВходнаяСтрока) Цикл
Символ = Сред(ВходнаяСтрока, Индекс, 1);
Если СтрНайти(ДопустимыеСимволы, Символ) > 0 Тогда
Результат = Результат + Символ;
КонецЕсли;
КонецЦикла;
// Берем последние 10 цифр номера (без префиксов 7, 8 или +7)
Длина = СтрДлина(Результат);
Если Длина >= 10 Тогда
Результат = Прав(Результат, 10);
КонецЕсли;
Возврат Результат;
КонецФункции
Теперь разберем, как применить нормализацию при поиске в памяти.
Для хранения связей в оперативной памяти лучше всего использовать объект Соответствие, так как поиск по ключу в нем происходит мгновенно:
КартаНомеров = Новый Соответствие;
// Заполняем карту при первичной загрузке контактов
Для Каждого СтрКонтакта Из ТаблицаКонтактов Цикл
ЧистыйНомер = ПолучитьТолькоЦифры(СтрКонтакта.НомерТелефона);
Если ЗначениеЗаполнено(ЧистыйНомер) Тогда
КартаНомеров.Вставить(ЧистыйНомер, СтрКонтакта.Контрагент);
КонецЕсли;
КонецЦикла;
// Поиск входящего абонента
ИскомыйНомер = ПолучитьТолькоЦифры("+7 (926) 123-45-67");
НайденныйКонтрагент = КартаНомеров[ИскомыйНомер];
Если НайденныйКонтрагент <> Неопределено Тогда
// Контрагент найден
КонецЕсли;
Если данные необходимо хранить исключительно в виде таблицы, добавим служебную колонку НомерДляПоиска и проиндексируем ее:
// Добавляем колонку и строим индекс
ТаблицаКонтактов.Колонки.Добавить("НомерДляПоиска", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(10)));
Для Каждого СтрокаТЗ Из ТаблицаКонтактов Цикл
СтрокаТЗ.НомерДляПоиска = ПолучитьТолькоЦифры(СтрокаТЗ.НомерТелефона);
КонецЦикла;
ТаблицаКонтактов.Индексы.Добавить("НомерДляПоиска");
// Выполняем быстрый поиск по индексу
Отбор = Новый Структура("НомерДляПоиска", ПолучитьТолькоЦифры(ВходящийНомер));
НайденныеСтроки = ТаблицаКонтактов.НайтиСтроки(Отбор);
Индексация колонки таблицы значений позволяет методу НайтиСтроки() работать без линейного перебора всех строк, обеспечивая высокую скорость даже на сотнях тысяч записей.