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