В процессе разработки на платформе 1С:Предприятие часто возникает задача получения ссылки на объект справочника, имея лишь его текстовое наименование. Стандартный метод НайтиПоНаименованию() прост в использовании, но имеет ряд ограничений: он не всегда эффективен при массовой обработке данных, может возвращать непредсказуемый результат при наличии дублей и зачастую порицается стандартами разработки из-за неявных обращений к базе данных в цикле. В этой статье мы подробно разберем альтернативные и более профессиональные способы решения этой задачи.
Наиболее гибким и правильным с точки зрения архитектуры 1С является использование объекта Запрос. Запрос позволяет не только найти элемент, но и четко контролировать ситуацию, когда в базе данных присутствует несколько элементов с одинаковым наименованием. Рассмотрим ситуацию, когда нам нужно найти ссылку на товар по строковой переменной ИмяТовара.
Проанализируем пример кода для поиска единственного значения:
ТекстЗапроса =
"ВЫБРАТЬ ПЕРВЫЕ 2
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование = &Наименование";
Запрос = Новый Запрос(ТекстЗапроса);
Запрос.УстановитьПараметр("Наименование", ИмяТовара);
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Количество() > 1 Тогда
Сообщить("Найдено несколько элементов с таким наименованием!");
КонецЕсли;
Пока Выборка.Следующий() Цикл
НужнаяСсылка = Выборка.Ссылка;
Прервать; // Берем только первый найденный
КонецЦикла;
КонецЕсли;
Использование конструкции ВЫБРАТЬ ПЕРВЫЕ 2 позволяет нам быстро понять, уникально ли наше наименование в системе. Если количество строк в результате равно двум, значит, в справочнике есть дубли, и логику поиска стоит уточнить (например, добавить поиск по родителю или коду).
Если перед нами стоит задача преобразовать не одну строку, а целый массив наименований (например, при загрузке данных из внешнего файла), выполнение запроса в цикле будет крайне неэффективным. В этом случае мы воспользуемся методикой пакетной обработки. Разберем этот алгоритм по шагам:
Соответствие, где ключом будет наименование, а значением — найденная ссылка.Рассмотрим реализацию этого подхода:
// Предположим, у нас есть ТаблицаДанных с колонкой "НаименованиеСтрокой"
МассивИмен = ТаблицаДанных.ВыгрузитьКолонку("НаименованиеСтрокой");
// Удалим дубли из массива для ускорения работы запроса
ОбщегоНазначенияКлиентСервер.СвернутьМассив(МассивИмен);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Наименование КАК Наименование,
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Наименование В(&МассивИмен)";
Запрос.УстановитьПараметр("МассивИмен", МассивИмен);
ТаблицаСсылок = Запрос.Выполнить().Выгрузить();
// Заполняем соответствие для быстрого поиска в памяти
СоответствиеИмен = Новый Соответствие;
Для Каждого Строка Из ТаблицаСсылок Цикл
СоответствиеИмен.Вставить(Строка.Наименование, Строка.Ссылка);
КонецЦикла;
// Теперь мы можем мгновенно получить ссылку для каждой строки таблицы
Для Каждого СтрокаТаблицы Из ТаблицаДанных Цикл
СтрокаТаблицы.Ссылка = СоответствиеИмен[СтрокаТаблицы.НаименованиеСтрокой];
КонецЦикла;
Этот метод в десятки раз быстрее поиска "в лоб", так как минимизирует количество обращений к серверу СУБД. Важный нюанс: для того чтобы такой поиск работал быстро, реквизит Наименование (или любой другой, по которому идет поиск) должен быть проиндексирован в метаданных справочника.
Иногда под "строкой" понимается строковое представление уникального идентификатора (GUID). В этом случае нам вообще не нужно выполнять поиск в базе данных через запросы. Мы можем сконструировать ссылку напрямую. Однако стоит учитывать, что полученная ссылка может быть "битой" (объекта с таким ID может не существовать в базе).
Посмотрим, как это сделать:
СтрокаGUID = "550e8400-e29b-41d4-a716-446655440000";
Попытка
НовыйИдентификатор = Новый УникальныйИдентификатор(СтрокаGUID);
СсылкаНаОбъект = Справочники.Номенклатура.ПолучитьСсылку(НовыйИдентификатор);
// Проверим, существует ли такой объект на самом деле
Если ОбщегоНазначения.СсылкаСуществует(СсылкаНаОбъект) Тогда
// Работаем со ссылкой
КонецЕсли;
Исключение
Сообщить("Некорректный формат GUID");
КонецПопытки;
Если в строке передается имя предопределенного элемента, заданного в конфигураторе, правильнее и быстрее всего использовать функцию ПредопределенноеЗначение(). Это системный механизм, который не требует написания запросов и работает максимально быстро.
Выясним, как получить ссылку по имени предопределенного узла:
Ссылка = ПредопределенноеЗначение("Справочник.Валюты.Доллар");
Если наименование в строке может содержать опечатки или вы знаете только часть названия, можно прибегнуть к механизму полнотекстового поиска. Это позволяет реализовать поиск, похожий на работу поисковых систем в интернете.
Рассмотрим пример использования программного интерфейса ПП:
СписокПоиска = ПолнотекстовыйПоиск.СоздатьСписок("Болтик М5", 10);
СписокПоиска.ОбластьПоиска.Добавить(Метаданные.Справочники.Номенклатура);
СписокПоиска.ПерваяЧасть();
Если СписокПоиска.Количество() > 0 Тогда
Для Каждого Результат Из СписокПоиска Цикл
НайденнаяСсылка = Результат.Значение;
// Логика обработки найденного
КонецЦикла;
КонецЕсли;
Проанализируем распространенную ошибку, упомянутую в обсуждении. При поиске через запрос крайне не рекомендуется использовать функции обрезки строк или приведения к регистру в условии ГДЕ или СОЕДИНЕНИЕ. Например, конструкция ПОДСТРОКА(Наименование, 1, 40) заставит СУБД игнорировать индексы и выполнять полное сканирование таблицы (Full Scan). Это приведет к значительным тормозам на больших объемах данных.
Основные правила эффективного преобразования:
Соответствие в памяти.ЗначениеЗаполнено() для проверки результата поиска, прежде чем обращаться к свойствам ссылки.Выбор конкретного метода зависит от ваших задач: для разового поиска идеально подойдет Запрос, для загрузки из Excel — пакетное сопоставление через Массив и Соответствие, а для системных констант — ПредопределенноеЗначение.