При разработке обработок загрузки данных из внешних источников (в частности, Microsoft Excel) разработчики нередко сталкиваются с ситуацией, когда метод НайтиПоНаименованию() возвращает пустую ссылку, хотя визуально строка поиска полностью совпадает с наименованием элемента в базе. Разберем причины такого поведения платформы, способы диагностики и правила корректной подготовки данных.
Рассмотрим синтаксис и поведение стандартного метода поиска по наименованию менеджера справочника:
// Синтаксис метода:
// НайтиПоНаименованию(<Наименование>, <ТочноеСоответствие>, <Родитель>, <Владелец>)
ФизЛицо = Справочники.ФизическиеЛица.НайтиПоНаименованию(ФИО, Истина);
Если параметр ТочноеСоответствие установлен в значение Истина, платформа выполняет поиск строки с полным совпадением (за исключением концевых пробелов). Если совпадений не найдено, метод возвращает пустую ссылку соответствующего типа (СправочникСсылка.ФизическиеЛица.ПустаяСсылка()), а если наименование в метаданных справочника имеет нулевую длину — значение Неопределено.
Когда поиск по полному совпадению завершается неудачно при видимом визуальном сходстве строк, причина практически всегда кроется в различии байтового состава строк. Выясним основные источники расхождений:
Символы.НПП). Для СУБД символы с кодами 32 и 160 являются абсолютно разными.Символы.Таб), мягкие переносы или невидимые служебные символы.Наименование, заданного в конфигураторе.Проанализируем базовый способ устранения неразрывных пробелов. Чтобы метод нашел элемент, необходимо заменить все неразрывные пробелы на стандартные с помощью функции СтрЗаменить(), а также удалить лишние пробелы по краям с помощью СокрЛП():
// Очистка от неразрывных пробелов и крайних пробелов
ФИО = СокрЛП(СтрЗаменить(ФИО, Символы.НПП, " "));
// Выполняем поиск
СсылкаНаФизЛицо = Справочники.ФизическиеЛица.НайтиПоНаименованию(ФИО, Истина);
Если Не СсылкаНаФизЛицо.Пустая() Тогда
// Элемент успешно найден
КонецЕсли;
Для гарантированной очистки строк от любых скрытых символов и двойных пробелов применим комплексную функцию предварительной обработки:
Функция НормализоватьСтрокуПоиска(Знач ВходнаяСтрока)
// Заменяем неразрывные пробелы и табуляцию
Результат = СтрЗаменить(ВходнаяСтрока, Символы.НПП, " ");
Результат = СтрЗаменить(Результат, Символы.Таб, " ");
Результат = СтрЗаменить(Результат, Символы.ВК, "");
Результат = СтрЗаменить(Результат, Символы.ПС, "");
// Сворачиваем множественные пробелы в один
Пока СтрНайти(Результат, " ") > 0 Цикл
Результат = СтрЗаменить(Результат, " ", " ");
КонецЦикла;
Возврат СокрЛП(Результат);
КонецФункции
Если в наименовании могут присутствовать расхождения в инициалах или окончаниях, мы можем отключить параметр точного соответствия. При значении параметра ТочноеСоответствие = Ложь платформа выполняет поиск по совпадению с начала строки (по левой части):
// Поиск по началу наименования
ФИО = НормализоватьСтрокуПоиска(ФИО);
СсылкаНаФизЛицо = Справочники.ФизическиеЛица.НайтиПоНаименованию(ФИО, Ложь);
Важное ограничение: если в справочнике присутствуют полные однофамильцы, метод вернет ссылку только на один произвольный элемент, так как платформа выполняет выборку первой попавшейся записи без гарантированного порядка.
Вызов НайтиПоНаименованию() внутри цикла обработки строк Excel создает проблему производительности (антипаттерн N+1 запросов к базе данных). При тысячах строк процесс загрузки будет выполняться крайне медленно.
Рассмотрим правильный подход: выгрузку нормализованных строк во временную таблицу и выполнение сопоставления единым запросом.
// 1. Формируем таблицу значений с данными из Excel
ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("НомерСтрокиФайла", Новый ОписаниеТипов("Число"));
ТаблицаДанных.Колонки.Добавить("ФИО", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(150)));
ТаблицаДанных.Колонки.Добавить("Телефон", Новый ОписаниеТипов("Строка", Новый КвалификаторыСтроки(50)));
// Заполняем таблицу данными с предварительной нормализацией
Для Каждого СтрокаФайла Из МассивДанныхФайла Цикл
НоваяСтрока = ТаблицаДанных.Добавить();
НоваяСтрока.НомерСтрокиФайла = СтрокаФайла.Номер;
НоваяСтрока.ФИО = НормализоватьСтрокуПоиска(СтрокаФайла.ФИО);
НоваяСтрока.Телефон = СтрокаФайла.Телефон;
КонецЦикла;
// 2. Выполняем пакетное сопоставление через запрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТаблицаДанных.НомерСтрокиФайла КАК НомерСтрокиФайла,
| ТаблицаДанных.ФИО КАК ФИО_Поиск,
| ТаблицаДанных.Телефон КАК Телефон,
| ФизическиеЛица.Ссылка КАК СсылкаНаФизЛицо
|ИЗ
| &ТаблицаДанных КАК ТаблицаДанных
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ФизическиеЛица КАК ФизическиеЛица
| ПО (ТаблицаДанных.ФИО = ФизическиеЛица.Наименование)
| И (НЕ ФизическиеЛица.ПометкаУдаления)";
Запрос.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
// 3. Обрабатываем полученный результат
Для Каждого Стр Из РезультатЗапроса Цикл
Если ЗначениеЗаполнено(Стр.СсылкаНаФизЛицо) Тогда
// Элемент найден в базе - выполняем запись телефона
Иначе
// Элемент не найден - логируем ошибку или создаем новый элемент
КонецЕсли;
КонецЦикла;