Как выполнить поиск элемента справочника по реквизиту неограниченной длины в 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке конфигураций и внешних обработок в платформе 1С:Предприятие часто возникает задача программного поиска элементов справочника по значению определенного реквизита. Для этого традиционно используется встроенный метод НайтиПоРеквизиту(). Однако при попытке найти элемент по реквизиту типа Строка неограниченной длины разработчики сталкиваются с ошибкой недопустимых параметров. Разберем по шагам, почему возникает эта ошибка, какие архитектурные ограничения лежат в ее основе и как правильно решить поставленную задачу.

В чем причина ошибки метода НайтиПоРеквизиту?

Проанализируем сигнатуру и системные требования метода менеджера справочника. Метод НайтиПоРеквизиту() принимает параметры: ИмяРеквизита (строка, точно совпадающая с именем в дереве метаданных) и ЗначениеРеквизита (искомое значение). При этом документация платформы четко определяет ограничение: тип реквизита может быть произвольным, за исключением ХранилищеЗначения и строк неограниченной длины.

Типичное заблуждение начинающих разработчиков заключается в попытке обрезать строку встроенной функцией Лев() прямо в параметре метода:


// Ошибочный вызов:
Склад = Справочники.Склады.НайтиПоРеквизиту(Лев("Комментарий", 200), Строка(ПараметрыСеанса.ТекущийПользователь.Ссылка));

Здесь допущены сразу две грубые ошибки:

  1. Первым параметром передается имя реквизита, а не его значение. Выражение Лев("Комментарий", 200) возвращает строку "Комментарий", но сам реквизит в структуре метаданных по-прежнему остается строкой неограниченной длины. Функция языка 1С не меняет тип поля в базе данных.
  2. Поля неограниченной длины физически размещаются в СУБД в специальных LOB-структурах (CLOB/BLOB). Для таких структур невозможно создать стандартный B-Tree индекс СУБД. Поскольку метод НайтиПоРеквизиту() рассчитан на индексированный поиск, платформа блокирует его вызов на системном уровне.

Способ 1. Использование запроса с функцией ВЫРАЗИТЬ (Рекомендуемый)

Рассмотрим основной и наиболее надежный способ обхода этого ограничения — использование языка запросов 1С. В запросах нельзя напрямую сравнивать поля неограниченной длины через операторы = или <>. Чтобы выполнить строгое сравнение, приведем реквизит к строке фиксированной длины с помощью функции ВЫРАЗИТЬ.

Посмотрим на пример правильного формирования запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |	Склады.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.Склады КАК Склады
    |ГДЕ
    |	ВЫРАЗИТЬ(Склады.Комментарий КАК Строка(200)) = &Комментарий
    |	И Склады.Спецодежда = ИСТИНА
    |	И Склады.Подразделение = &Подразделение";

Запрос.УстановитьПараметр("Комментарий", СтрокаДляПоиска);
Запрос.УстановитьПараметр("Подразделение", НужноеПодразделение);

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    НайденнаяСсылка = Выборка.Ссылка;
КонецЕсли;

Разберем подробнее, что здесь происходит: конструкция ВЫРАЗИТЬ(Склады.Комментарий КАК Строка(200)) на уровне базы данных преобразует текст в фиксированную строку, что позволяет СУБД выполнить точное сравнение со значением параметра. Конструкция ВЫБРАТЬ ПЕРВЫЕ 1 оптимизирует выборку, прекращая поиск сразу после обнаружения первого подходящего элемента.

Способ 2. Поиск по части строки через оператор ПОДОБНО

Если требуется найти элемент, когда известно только частичное вхождение текста в комментарий, используем оператор ПОДОБНО. Этот оператор допустим для строк неограниченной длины без обязательного вызова функции ВЫРАЗИТЬ.

Посмотрим на реализацию запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |	Склады.Ссылка КАК Ссылка
    |ИЗ
    |	Справочник.Склады КАК Склады
    |ГДЕ
    |	Склады.Комментарий ПОДОБНО &МаскаПоиска";

Запрос.УстановитьПараметр("МаскаПоиска", "%" + СтрокаДляПоиска + "%");

Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
    Выборка = Результат.Выбрать();
    Выборка.Следующий();
    НайденныйСклад = Выборка.Ссылка;
КонецЕсли;

Важное предостережение: использование оператора ПОДОБНО для полей неограниченной длины (особенно с символом % в начале шаблона) неизбежно приводит к полному сканированию таблицы (Table Scan). При большом объеме записей в справочнике такой запрос будет выполняться медленно и создавать блокировки.

Способ 3. Изменение структуры метаданных и создание служебного реквизита

Проанализируем архитектурную сторону проблемы. Если по реквизиту регулярно требуется выполнять быстрый точечный поиск, хранить в нем строку неограниченной длины нецелесообразно. Рассмотрим два варианта доработки конфигурации:

  1. Ограничение длины основного реквизита: В дереве метаданных конфигуратора откроем свойства реквизита Комментарий, снимем признак Неограниченная длина и зададим фиксированную длину (например, 200 символов). После этого стандартный метод Справочники.Склады.НайтиПоРеквизиту("Комментарий", ИскомоеЗначение) будет работать штатно.
  2. Добавление индексированного служебного реквизита: Если комментарий действительно должен оставаться неограниченным, добавим отдельный реквизит (например, КлючПоиска с типом Строка(150)) и установим для него свойство Индексировать в значение Индексировать. В модуле объекта справочника в процедуре ПередЗаписью пропишем автоматическое заполнение:
    
    Процедура ПередЗаписью(Отказ)
        КлючПоиска = Лев(СокрЛП(Комментарий), 150);
    КонецПроцедуры
    
    После этого поиск можно легко и быстро выполнять системным методом:
    
    НайденныйЭлемент = Справочники.Склады.НайтиПоРеквизиту("КлючПоиска", ЗначениеПоиска);
    

Способ 4. Механизм полнотекстового поиска

Если поиск необходимо выполнять по длинным текстовым массивам без перегрузки СУБД полным сканированием таблиц, воспользуемся механизмом полнотекстового поиска платформы. При включенном полнотекстовом индексировании поиск выполняется средствами специализированного индекса:


Список = ПолнотекстовыйПоиск.СоздатьСписок(ИскомаяСтрока);
Список.ОбластьПоиска.Добавить(Метаданные.Справочники.Склады);
Список.ПерваяЧасть();

Для Каждого Элемент Из Список Цикл
    НайденныйСклад = Элемент.Значение;
    Прервать; // Берем первое совпадение
КонецЦикла;

Этот метод не требует прямого запроса через ПОДОБНО и обеспечивает высокую скорость отклика даже при миллионах записей.

Архитектурная ошибка: поиск ссылок по их строковому представлению

В тексте обсуждения автор пытался выполнить поиск пользователя через выражение Строка(ПараметрыСеанса.ТекущийПользователь.Ссылка). Выясним, почему так делать категорически нельзя. Функция Строка() возвращает строковое представление объекта, которое зависит от наименования или формата отображения. Если имя пользователя изменится, строковое представление перестанет совпадать со старыми записями.

Если справочник Склады должен быть привязан к конкретному пользователю, в справочнике необходимо создать отдельный реквизит ОтветственныйПользователь с типом СправочникСсылка.Пользователи и включить его индексирование. Тогда поиск будет выполняться строго по ссылке, максимально быстро и без ошибок преобразования типов:


СкладПользователя = Справочники.Склады.НайтиПоРеквизиту("ОтветственныйПользователь", ПараметрыСеанса.ТекущийПользователь);
← На главную