При разработке интеграций, загрузок прайс-листов или обменов данными мы часто сталкиваемся с задачей поиска элементов справочника по наименованию. В конфигурациях 1С (например, «Управление торговлей 10.3», «УПП» или «Комплексная автоматизация») у справочника Номенклатура часто бывает два реквизита: Наименование (краткое, строка ограниченной длины) и НаименованиеПолное (строка неограниченной длины).
Многие разработчики пытаются использовать стандартный метод менеджера справочника НайтиПоРеквизиту, но сталкиваются с ошибкой выполнения. Давайте разберем, почему это происходит, и рассмотрим надежные способы решения этой задачи.
Рассмотрим типичную ситуацию. У нас есть таблица значений (например, из Excel), где указано полное название товара. Мы пытаемся найти товар следующим кодом:
// ОШИБОЧНЫЙ КОД
Номенклатура = Справочники.Номенклатура.НайтиПоРеквизиту("НаименованиеПолное", ИскомаяСтрока);
В результате платформа выдает ошибку: «Ошибка при вызове метода контекста (НайтиПоРеквизиту): Неверно указан реквизит».
Причина кроется в устройстве базы данных. Метод НайтиПоРеквизиту работает быстро, потому что использует индексы СУБД. Реквизит НаименованиеПолное имеет тип Строка неограниченной длины. В терминах SQL это типы данных (text, varchar(max) и подобные), которые не подлежат обычному индексированию из-за своего размера. Платформа 1С заранее знает об этом ограничении и блокирует попытку поиска по такому полю еще на этапе исполнения кода.
Разберем работающие способы обхода этого ограничения.
Самый доступный способ, не требующий изменения конфигурации — использование языка запросов. Поскольку напрямую сравнивать строки неограниченной длины в условии ГДЕ не всегда оптимально или возможно (в зависимости от СУБД), мы должны использовать оператор ВЫРАЗИТЬ (CAST), чтобы привести строку к ограниченной длине.
Давайте напишем функцию поиска:
Функция НайтиПоПолномуНаименованию(ИскомоеНаименование)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| ВЫРАЗИТЬ(Номенклатура.НаименованиеПолное КАК СТРОКА(1000)) = &НаименованиеПолное";
// Обрезаем искомую строку до 1000 символов, чтобы совпадало с условием запроса
СтрокаПоиска = Лев(СокрЛП(ИскомоеНаименование), 1000);
Запрос.УстановитьПараметр("НаименованиеПолное", СтрокаПоиска);
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Возврат Результат.Выгрузить()[0].Ссылка;
Иначе
Возврат Справочники.Номенклатура.ПустаяСсылка();
КонецЕсли;
КонецФункции
В этом примере мы приводим НаименованиеПолное к строке длиной 1000 символов. Этого обычно достаточно для уникальной идентификации товара. Обратите внимание: использование функций над полями в секции ГДЕ может привести к полному сканированию таблицы (Full Table Scan), что на больших базах может работать медленно.
Если нам нужно найти не одну номенклатуру, а сразу сопоставить большую таблицу (например, при загрузке накладной на 1000 строк), выполнять запрос в цикле нельзя — это «убьет» производительность. В этом случае мы используем пакетный запрос. При переносе справочников между базами задачу сопоставления решает обработка переноса данных между похожими конфигурациями 1С, поддерживающая поиск по произвольным реквизитам.
Алгоритм действий:
ВЫРАЗИТЬ.
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТЗ", ТЗ_ИзФайла); // В ТЗ должна быть колонка НаименованиеПолное
Запрос.Текст =
"ВЫБРАТЬ
| ВходящиеДанные.НомерСтроки КАК НомерСтроки,
| ВходящиеДанные.НаименованиеПолное КАК НаименованиеПолное
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТЗ КАК ВходящиеДанные
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Данные.НомерСтроки,
| ВТ_Данные.НаименованиеПолное,
| ЕСТЬNULL(СпрНоменклатура.Ссылка, ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)) КАК НоменклатураСсылка
|ИЗ
| ВТ_Данные КАК ВТ_Данные
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК СпрНоменклатура
| ПО (ВЫРАЗИТЬ(ВТ_Данные.НаименованиеПолное КАК СТРОКА(1000)) = ВЫРАЗИТЬ(СпрНоменклатура.НаименованиеПолное КАК СТРОКА(1000)))";
Выборка = Запрос.Выполнить().Выбрать();
Важное предупреждение: Данный метод также ресурсоемкий. SQL-серверу придется преобразовать поле НаименованиеПолное для каждой записи в справочнике номенклатуры, чтобы выполнить соединение. Если в базе 100 000 товаров, запрос может выполняться ощутимое время.
Если у вас есть возможность вносить изменения в конфигурацию, рассмотрите вариант изменения типа реквизита НаименованиеПолное.
Вместо Строка неограниченной длины установите Строка (1024) (или другую разумную длину). После этого:
НайтиПоРеквизиту начнет работать штатно и очень быстро.Однако, будьте осторожны: если в базе уже есть товары с описанием длиннее 1024 символов, при реструктуризации эти данные будут обрезаны.
Для высоконагруженных систем, где поиск по полному наименованию выполняется часто, профессиональным решением является добавление служебного реквизита для хранения хеш-суммы.
Как это реализовать:
Номенклатура реквизит ХешНаименования (Строка, 32 символа, Индексировать).ПередЗаписью вычисляем MD5-хеш от полного наименования.
// Пример кода в модуле объекта Справочника Номенклатура
Процедура ПередЗаписью(Отказ)
ХешФункция = Новый ХешированиеДанных(ХешФункция.MD5);
// Нормализуем строку перед хешированием (убираем лишние пробелы и приводим к нижнему регистру)
ТекстДляХеша = НРег(СокрЛП(НаименованиеПолное));
ХешФункция.Добавить(ТекстДляХеша);
// Записываем результат в новый реквизит
ЭтотОбъект.ХешНаименования = Строка(ХешФункция.ХешСумма);
EndProcedure
Теперь поиск становится молниеносным, так как мы ищем по короткой индексированной строке:
// Вычисляем хеш искомой строки
ХешФункция = Новый ХешированиеДанных(ХешФункция.MD5);
ХешФункция.Добавить(НРег(СокрЛП(ИскомаяСтрока)));
ИскомыйХеш = Строка(ХешФункция.ХешСумма);
// Мгновенный поиск
НайденнаяСсылка = Справочники.Номенклатура.НайтиПоРеквизиту("ХешНаименования", ИскомыйХеш);
При поиске, особенно если данные приходят из внешних источников (Excel, XML), часто возникают проблемы с невидимыми символами. Визуально строки одинаковы, но программно они не равны. При регулярном импорте торговых документов сопоставление объектов автоматизирует обработка загрузки отчётов о продажах в БП 3 из Excel, которая идентифицирует номенклатуру и загружает её в бухгалтерский учёт.
Рекомендуем перед поиском (или вычислением хеша) очищать строку:
СокрЛП() для удаления пробелов по краям.
Функция НормализоватьСтроку(ВходнаяСтрока)
// Заменяем неразрывный пробел на обычный
СтрокаБезНП = СтрЗаменить(ВходнаяСтрока, Символ(160), " ");
// Удаляем лишние пробелы и переводы строк
Возврат СокрЛП(СтрокаБезНП);
КонецФункции
Применяйте эти методы в зависимости от ваших прав доступа к конфигурации и требований к производительности системы.