Поиск и выбор элементов справочника во встроенном языке 1С 8.3

· 10 мин

Содержание

Чтобы получить ссылку на элемент справочника в коде, выбирают один из трёх методов менеджера: НайтиПоКоду() — когда известен код, НайтиПоНаименованию() — когда известно имя, НайтиПоРеквизиту() — когда ищем по значению поля (ИНН, артикул, email). Все три возвращают ссылку, а если ничего не нашли — пустую ссылку того же типа. Для выбора значения пользователем в форме отдельный код не нужен: реквизит с типом СправочникСсылка сам рисует поле с кнопкой выбора.

Разница между методами — в том, что известно на входе и сколько элементов может подойти. Код уникален почти всегда, наименование и реквизит — не обязательно, и тогда метод вернёт только первый совпавший. Для перебора нескольких элементов или выбора по сложному условию используют выборку Выбрать() или запрос. Ниже — когда что применять, как не получить чужой элемент и где теряется скорость на больших справочниках.

Поиск по коду, наименованию и реквизиту: что выбрать

Методы поиска вызывают у менеджера справочника — Справочники.Контрагенты, Справочники.Номенклатура и так далее.

По коду — самый надёжный способ, если код действительно уникален:

НайденныйКонтрагент = Справочники.Контрагенты.НайтиПоКоду("00-0001234");

Код справочника по умолчанию строковый, даже если выглядит как число. Передавайте строку, а не число — иначе получите пустую ссылку на ровном месте.

По наименованию — когда код неизвестен, а имя точно совпадает с тем, что в базе:

Валюта = Справочники.Валюты.НайтиПоНаименованию("USD", Истина);

Второй параметр — ТочноеСоответствие. Если его не указать, он равен Ложь, и поиск идёт по началу строки: «Иванов» найдёт и «Иванова», и «Ивановский». Для точного поиска всегда ставьте Истина.

По реквизиту — когда ищем по значению поля, а не по коду или имени:

Контрагент = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", "7707083893");

Метод возвращает первый найденный элемент. Если ИНН в базе задвоен, какой из двух придёт — не гарантируется. Когда совпадений может быть несколько и нужны все — это уже не задача для НайтиПоРеквизиту, берите запрос (см. ниже).

Коротко, чем методы отличаются на практике:

МетодЧто на входеСколько вернётКогда брать
НайтиПоКодуКод (строка)ОдинКод известен и уникален
НайтиПоНаименованиюИмяПервый совпавшийИмя точное и уникальное
НайтиПоРеквизитуПоле и значениеПервый совпавшийОдин элемент по ИНН/артикулу
ЗапросЛюбое условиеВсе подходящиеСовпадений несколько или условие сложное

У всех трёх методов НайтиПо... есть необязательные параметры Родитель и Владелец. Для подчинённого справочника (например, «Контактные лица», подчинённые «Контрагентам») без указания владельца поиск идёт по всей базе и может вернуть элемент чужого контрагента. Передавайте владельца четвёртым параметром, когда ищете в подчинённом справочнике.

Какой способ поиска выбрать под задачу?

Перебор элементов: выборка и запрос

Когда нужен не один элемент, а несколько или все, поиск по коду не подходит. Есть два инструмента.

Выборка Выбрать() обходит элементы справочника по одному:

Выборка = Справочники.Номенклатура.Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Наименование);
КонецЦикла;

Следующий() возвращает Истина, пока есть элементы, и переставляет указатель на очередную строку. Внутри цикла доступны Выборка.Ссылка, Выборка.Код, Выборка.Наименование и любые реквизиты. Отбор задают структурой:

Отбор = Новый Структура("Поставщик", ВыбранныйПоставщик);
Выборка = Справочники.Номенклатура.Выбрать(, , Отбор);

Минус выборки — она читает элементы целиком и работает на клиенте менеджера объектов. На справочнике в десятки тысяч строк это медленно.

Запрос быстрее и гибче: условия, соединения, итоги считает СУБД, а в код приходит готовый результат.

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ
    |    Контрагенты.Ссылка КАК Ссылка,
    |    Контрагенты.Наименование КАК Наименование
    |ИЗ
    |    Справочник.Контрагенты КАК Контрагенты
    |ГДЕ
    |    Контрагенты.ИНН = &ИНН
    |    И НЕ Контрагенты.ПометкаУдаления";
Запрос.УстановитьПараметр("ИНН", ИНН);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Обработать(Выборка.Ссылка);
КонецЦикла;

Значения в текст запроса подставляют только через УстановитьПараметр, а не склейкой строк. Склейка ломается на кавычках и открывает путь к ошибкам выполнения.

Когда элемент нужен по известному уникальному идентификатору (например, ссылку сохранили как строку GUID во внешней системе), поиск не нужен вовсе — ссылку восстанавливают напрямую:

Ид = Новый УникальныйИдентификатор(СтрокаGUID);
Ссылка = Справочники.Контрагенты.ПолучитьСсылку(Ид);

Метод не проверяет, существует ли такой элемент на самом деле: он только собирает ссылку. Прочитайте любой реквизит через неё или проверьте запросом, если нужно убедиться, что объект есть в базе.

Если нужно просто проверить, есть ли хоть один подходящий элемент, не тяните все строки: добавьте в запрос ПЕРВЫЕ 1 и проверьте результат на пустоту методом Результат.Пустой().

Поиск в иерархических справочниках

В иерархическом справочнике элементы лежат в группах. Группа — такой же элемент, но с признаком ЭтоГруппа = Истина, и у неё есть подчинённые. Отличить группу от элемента можно прямо по ссылке:

Если Ссылка.ЭтоГруппа Тогда
    // это папка, а не товар
КонецЕсли;

Чтобы выбрать элементы только одной группы, передайте её ссылку первым параметром Выбрать():

Выборка = Справочники.Номенклатура.Выбрать(ГруппаЭлектроника);

Для обхода всего дерева, включая вложенные группы, есть ВыбратьИерархически():

Выборка = Справочники.Номенклатура.ВыбратьИерархически();
Пока Выборка.Следующий() Цикл
    Если Не Выборка.ЭтоГруппа Тогда
        Обработать(Выборка.Ссылка);
    КонецЕсли;
КонецЦикла;

Другой приём обхода дерева — рекурсия через ВыбратьПодчинённые(): метод возвращает выборку детей текущего элемента, и процедура вызывает сама себя для каждой группы.

Процедура ОбойтиГруппу(Родитель)
    Выборка = Справочники.Номенклатура.ВыбратьПодчинённые(Родитель);
    Пока Выборка.Следующий() Цикл
        Если Выборка.ЭтоГруппа Тогда
            ОбойтиГруппу(Выборка.Ссылка);
        Иначе
            Обработать(Выборка.Ссылка);
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Для одной конкретной группы такой обход нагляден, но на всё дерево запрос с В ИЕРАРХИИ быстрее: он снимает данные за один проход, а не ходит в базу на каждом уровне.

В запросе по иерархии работает условие В ИЕРАРХИИ:

ГДЕ Номенклатура.Ссылка В ИЕРАРХИИ (&Группа)

Оно вернёт все элементы указанной группы и всех её подгрупп одним запросом — это быстрее ручного обхода выборкой.

У справочника может быть иерархия групп и элементов или иерархия только элементов. Прежде чем опираться на ЭтоГруппа, посмотрите в конфигураторе свойство Иерархический и вид иерархии — иначе логика отработает не так, как ожидали.

Пустая ссылка: как проверить, что элемент найден

Методы НайтиПо... при неудаче возвращают не Неопределено, а пустую ссылку того же типа. Это главная ловушка новичка:

// НЕПРАВИЛЬНО — пустая ссылка не равна Неопределено, условие не сработает
Если Контрагент = Неопределено Тогда ...

// ПРАВИЛЬНО
Если Контрагент.Пустая() Тогда
    Сообщить("Контрагент не найден");
КонецЕсли;

Второй надёжный способ — ЗначениеЗаполнено(): для пустой ссылки он вернёт Ложь.

Если Не ЗначениеЗаполнено(Контрагент) Тогда
    Контрагент = СоздатьНовогоКонтрагента();
КонецЕсли;

Явно получить пустую ссылку нужного типа — например, чтобы очистить реквизит — можно методом ПустаяСсылка():

Элемент.ОсновнойДоговор = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();

Разница между проверками важна в тонком случае: Пустая() вернёт Истина и для пустой ссылки, и для только что созданного, но ещё не записанного элемента. ЗначениеЗаполнено() учитывает ещё и пометку удаления по-разному в зависимости от типа — для бизнес-логики «есть значение или нет» он обычно удобнее.

Производительность: где теряется скорость при выборе

На маленьком справочнике разница незаметна. На большом — критична. Правила простые.

  • 🔴 Не вызывайте НайтиПо... в цикле. Каждый вызов — отдельный запрос к СУБД. Тысяча строк — тысяча запросов. Соберите все нужные ключи и возьмите данные одним запросом с условием В (&СписокЗначений):
СписокИНН = Новый Массив;
Для Каждого Строка Из ТаблицаДанных Цикл
    СписокИНН.Добавить(Строка.ИНН);
КонецЦикла;

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ Контрагенты.ИНН, Контрагенты.Ссылка
    |ИЗ Справочник.Контрагенты КАК Контрагенты
    |ГДЕ Контрагенты.ИНН В (&Список)";
Запрос.УстановитьПараметр("Список", СписокИНН);
СсылкиПоИНН = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    СсылкиПоИНН.Вставить(Выборка.ИНН, Выборка.Ссылка);
КонецЦикла;

Дальше в цикле берёте ссылку из Соответствие мгновенно, без обращений к базе.

  • 🔴 Не обходите выборкой то, что решает запрос. Выбрать() читает элементы целиком; запрос вернёт только нужные поля и только подходящие строки.
  • 🟡 Ищите по индексированным реквизитам. НайтиПоРеквизиту и условие в запросе по неиндексированному полю заставляют СУБД перебирать всю таблицу. Нужно часто искать по реквизиту — поставьте ему свойство Индексировать в конфигураторе.
  • 🟡 Не читайте объект там, где хватает ссылки. Выборка.ПолучитьОбъект() и Ссылка.ПолучитьОбъект() поднимают из базы весь элемент с табличными частями. Если нужны одно-два поля, читайте их прямо через ссылку (Ссылка.Наименование) — это использует кэш и дешевле.

Отдельно о предопределённых элементах. Рубль, ставку «Без НДС», типовой вид контактной информации не ищут по коду или имени в коде — коды и имена могут поменяться. Берите их по внутреннему имени:

Рубль = ПредопределённоеЗначение("Справочник.Валюты.Рубль");

Так ссылка резолвится по метаданным, без запроса к данным, и не сломается при смене кода.

Сомневаетесь, что тормозит, — включите замер производительности или технологический журнал по событию DBMSSQL и посмотрите реальные запросы к СУБД. Гадать по коду на глаз дольше, чем снять замер.

Выбор значения справочника в форме

Если справочник выбирает пользователь, писать поиск вручную не нужно. Достаточно реквизита (объекта или формы) с типом СправочникСсылка.<Имя> — платформа сама покажет поле ввода с кнопкой выбора и выпадающим списком. Открывать и обрабатывать форму выбора вручную приходится редко.

Чаще нужно ограничить, что пользователь может выбрать. Для этого есть два штатных механизма.

  1. Отбор в параметрах выбора реквизита. Задаётся в конфигураторе (свойство Параметры выбора) или программно через ПараметрыВыбора элемента формы. Список сразу откроется уже отфильтрованным.
  2. Связи параметров выбора — когда допустимые значения одного поля зависят от другого (договор зависит от контрагента). Настраиваются свойством Связи параметров выбора.

Программно форму выбора открывают, когда нужен нестандартный сценарий:

ОткрытьФорму("Справочник.Номенклатура.ФормаВыбора", , Элемент);

Выбранное значение приходит в обработчик ОбработкаВыбора элемента формы. Там его и присваивают реквизиту.

Отбор в параметрах выбора — это удобство интерфейса, а не защита данных. Пользователь с правами может подставить значение и в обход формы. Критичные ограничения дублируйте проверкой при записи объекта.

Частые ошибки при поиске элементов справочника

Диагностика: выберите симптом

Проверьте себя

Проверьте себя
Что вернёт `НайтиПоКоду()`, если элемент с таким кодом не существует?
Чем проверять, что поиск нашёл элемент?
Что быстрее при переборе тысяч элементов по условию?
Как получить предопределённый элемент справочника?

Частые вопросы

Чем `Пустая()` отличается от `ЗначениеЗаполнено()`?
Пустая() проверяет только, что ссылка не указывает на элемент. ЗначениеЗаполнено() — универсальная функция для значений любого типа, её удобнее применять в общей бизнес-логике. Для ссылки обе дадут одинаковый результат на «нашли / не нашли».
Как найти элемент сразу по двум реквизитам?
НайтиПоРеквизиту умеет искать только по одному полю. По нескольким условиям — запрос с перечислением условий через И в секции ГДЕ.
Почему нельзя искать предопределённый элемент по коду?
Код и наименование предопределённого элемента администратор может изменить, а внутреннее имя — нет. Поиск по коду сломается после правки справочника, ПредопределённоеЗначение — нет.
Выбрать() или запрос — что брать по умолчанию?
Для чтения и отбора данных — запрос: он быстрее и работает на стороне СУБД. Выбрать() оправдан, когда нужно обойти справочник и для каждого элемента вызвать ПолучитьОбъект(), чтобы изменить и записать.
Как в иерархическом справочнике получить все элементы группы вместе с подгруппами?
В запросе — условие Ссылка В ИЕРАРХИИ (&Группа). В объектной модели — метод ВыбратьИерархически() с проверкой ЭтоГруппа, чтобы пропускать папки.