Поиск и выбор элементов справочника во встроенном языке 1С 8.3
Содержание
Чтобы получить ссылку на элемент справочника в коде, выбирают один из трёх методов менеджера: НайтиПоКоду() — когда известен код, НайтиПоНаименованию() — когда известно имя, НайтиПоРеквизиту() — когда ищем по значению поля (ИНН, артикул, email). Все три возвращают ссылку, а если ничего не нашли — пустую ссылку того же типа. Для выбора значения пользователем в форме отдельный код не нужен: реквизит с типом СправочникСсылка сам рисует поле с кнопкой выбора.
Разница между методами — в том, что известно на входе и сколько элементов может подойти. Код уникален почти всегда, наименование и реквизит — не обязательно, и тогда метод вернёт только первый совпавший. Для перебора нескольких элементов или выбора по сложному условию используют выборку Выбрать() или запрос. Ниже — когда что применять, как не получить чужой элемент и где теряется скорость на больших справочниках.
Поиск по коду, наименованию и реквизиту: что выбрать
Методы поиска вызывают у менеджера справочника — Справочники.Контрагенты, Справочники.Номенклатура и так далее.
По коду — самый надёжный способ, если код действительно уникален:
НайденныйКонтрагент = Справочники.Контрагенты.НайтиПоКоду("00-0001234");
Код справочника по умолчанию строковый, даже если выглядит как число. Передавайте строку, а не число — иначе получите пустую ссылку на ровном месте.
По наименованию — когда код неизвестен, а имя точно совпадает с тем, что в базе:
Валюта = Справочники.Валюты.НайтиПоНаименованию("USD", Истина);
Второй параметр — ТочноеСоответствие. Если его не указать, он равен Ложь, и поиск идёт по началу строки: «Иванов» найдёт и «Иванова», и «Ивановский». Для точного поиска всегда ставьте Истина.
По реквизиту — когда ищем по значению поля, а не по коду или имени:
Контрагент = Справочники.Контрагенты.НайтиПоРеквизиту("ИНН", "7707083893");
Метод возвращает первый найденный элемент. Если ИНН в базе задвоен, какой из двух придёт — не гарантируется. Когда совпадений может быть несколько и нужны все — это уже не задача для НайтиПоРеквизиту, берите запрос (см. ниже).
Коротко, чем методы отличаются на практике:
| Метод | Что на входе | Сколько вернёт | Когда брать |
|---|---|---|---|
НайтиПоКоду | Код (строка) | Один | Код известен и уникален |
НайтиПоНаименованию | Имя | Первый совпавший | Имя точное и уникальное |
НайтиПоРеквизиту | Поле и значение | Первый совпавший | Один элемент по ИНН/артикулу |
| Запрос | Любое условие | Все подходящие | Совпадений несколько или условие сложное |
У всех трёх методов НайтиПо... есть необязательные параметры Родитель и Владелец. Для подчинённого справочника (например, «Контактные лица», подчинённые «Контрагентам») без указания владельца поиск идёт по всей базе и может вернуть элемент чужого контрагента. Передавайте владельца четвёртым параметром, когда ищете в подчинённом справочнике.
НайтиПоКоду(). Самый быстрый и предсказуемый вариант. Код передавайте строкой.НайтиПоНаименованию() со вторым параметром Истина. Если имена могут повторяться — результат ненадёжен, берите реквизит или запрос.НайтиПоРеквизиту("ИмяРеквизита", Значение). Вернёт первый совпавший.ГДЕ. Один проход по базе, получаете все подходящие строки.ПредопределённоеЗначение("Справочник.Валюты.Рубль"). Не ищите предопределённые по коду или имени — коды меняются.Перебор элементов: выборка и запрос
Когда нужен не один элемент, а несколько или все, поиск по коду не подходит. Есть два инструмента.
Выборка Выбрать() обходит элементы справочника по одному:
Выборка = Справочники.Номенклатура.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Наименование);
КонецЦикла;
Следующий() возвращает Истина, пока есть элементы, и переставляет указатель на очередную строку. Внутри цикла доступны Выборка.Ссылка, Выборка.Код, Выборка.Наименование и любые реквизиты. Отбор задают структурой:
Отбор = Новый Структура("Поставщик", ВыбранныйПоставщик);
Выборка = Справочники.Номенклатура.Выбрать(, , Отбор);
Минус выборки — она читает элементы целиком и работает на клиенте менеджера объектов. На справочнике в десятки тысяч строк это медленно.
Запрос быстрее и гибче: условия, соединения, итоги считает СУБД, а в код приходит готовый результат.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка,
| Контрагенты.Наименование КАК Наименование
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН
| И НЕ Контрагенты.ПометкаУдаления";
Запрос.УстановитьПараметр("ИНН", ИНН);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Обработать(Выборка.Ссылка);
КонецЦикла;
Значения в текст запроса подставляют только через УстановитьПараметр, а не склейкой строк. Склейка ломается на кавычках и открывает путь к ошибкам выполнения.
Когда элемент нужен по известному уникальному идентификатору (например, ссылку сохранили как строку GUID во внешней системе), поиск не нужен вовсе — ссылку восстанавливают напрямую:
Ид = Новый УникальныйИдентификатор(СтрокаGUID);
Ссылка = Справочники.Контрагенты.ПолучитьСсылку(Ид);
Метод не проверяет, существует ли такой элемент на самом деле: он только собирает ссылку. Прочитайте любой реквизит через неё или проверьте запросом, если нужно убедиться, что объект есть в базе.
Если нужно просто проверить, есть ли хоть один подходящий элемент, не тяните все строки: добавьте в запрос ПЕРВЫЕ 1 и проверьте результат на пустоту методом Результат.Пустой().
Поиск в иерархических справочниках
В иерархическом справочнике элементы лежат в группах. Группа — такой же элемент, но с признаком ЭтоГруппа = Истина, и у неё есть подчинённые. Отличить группу от элемента можно прямо по ссылке:
Если Ссылка.ЭтоГруппа Тогда
// это папка, а не товар
КонецЕсли;
Чтобы выбрать элементы только одной группы, передайте её ссылку первым параметром Выбрать():
Выборка = Справочники.Номенклатура.Выбрать(ГруппаЭлектроника);
Для обхода всего дерева, включая вложенные группы, есть ВыбратьИерархически():
Выборка = Справочники.Номенклатура.ВыбратьИерархически();
Пока Выборка.Следующий() Цикл
Если Не Выборка.ЭтоГруппа Тогда
Обработать(Выборка.Ссылка);
КонецЕсли;
КонецЦикла;
Другой приём обхода дерева — рекурсия через ВыбратьПодчинённые(): метод возвращает выборку детей текущего элемента, и процедура вызывает сама себя для каждой группы.
Процедура ОбойтиГруппу(Родитель)
Выборка = Справочники.Номенклатура.ВыбратьПодчинённые(Родитель);
Пока Выборка.Следующий() Цикл
Если Выборка.ЭтоГруппа Тогда
ОбойтиГруппу(Выборка.Ссылка);
Иначе
Обработать(Выборка.Ссылка);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Для одной конкретной группы такой обход нагляден, но на всё дерево запрос с В ИЕРАРХИИ быстрее: он снимает данные за один проход, а не ходит в базу на каждом уровне.
В запросе по иерархии работает условие В ИЕРАРХИИ:
ГДЕ Номенклатура.Ссылка В ИЕРАРХИИ (&Группа)
Оно вернёт все элементы указанной группы и всех её подгрупп одним запросом — это быстрее ручного обхода выборкой.
У справочника может быть иерархия групп и элементов или иерархия только элементов. Прежде чем опираться на ЭтоГруппа, посмотрите в конфигураторе свойство Иерархический и вид иерархии — иначе логика отработает не так, как ожидали.
Пустая ссылка: как проверить, что элемент найден
Методы НайтиПо... при неудаче возвращают не Неопределено, а пустую ссылку того же типа. Это главная ловушка новичка:
// НЕПРАВИЛЬНО — пустая ссылка не равна Неопределено, условие не сработает
Если Контрагент = Неопределено Тогда ...
// ПРАВИЛЬНО
Если Контрагент.Пустая() Тогда
Сообщить("Контрагент не найден");
КонецЕсли;
Второй надёжный способ — ЗначениеЗаполнено(): для пустой ссылки он вернёт Ложь.
Если Не ЗначениеЗаполнено(Контрагент) Тогда
Контрагент = СоздатьНовогоКонтрагента();
КонецЕсли;
Явно получить пустую ссылку нужного типа — например, чтобы очистить реквизит — можно методом ПустаяСсылка():
Элемент.ОсновнойДоговор = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();
Разница между проверками важна в тонком случае: Пустая() вернёт Истина и для пустой ссылки, и для только что созданного, но ещё не записанного элемента. ЗначениеЗаполнено() учитывает ещё и пометку удаления по-разному в зависимости от типа — для бизнес-логики «есть значение или нет» он обычно удобнее.
Производительность: где теряется скорость при выборе
На маленьком справочнике разница незаметна. На большом — критична. Правила простые.
- 🔴 Не вызывайте
НайтиПо...в цикле. Каждый вызов — отдельный запрос к СУБД. Тысяча строк — тысяча запросов. Соберите все нужные ключи и возьмите данные одним запросом с условиемВ (&СписокЗначений):
СписокИНН = Новый Массив;
Для Каждого Строка Из ТаблицаДанных Цикл
СписокИНН.Добавить(Строка.ИНН);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ Контрагенты.ИНН, Контрагенты.Ссылка
|ИЗ Справочник.Контрагенты КАК Контрагенты
|ГДЕ Контрагенты.ИНН В (&Список)";
Запрос.УстановитьПараметр("Список", СписокИНН);
СсылкиПоИНН = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СсылкиПоИНН.Вставить(Выборка.ИНН, Выборка.Ссылка);
КонецЦикла;
Дальше в цикле берёте ссылку из Соответствие мгновенно, без обращений к базе.
- 🔴 Не обходите выборкой то, что решает запрос.
Выбрать()читает элементы целиком; запрос вернёт только нужные поля и только подходящие строки. - 🟡 Ищите по индексированным реквизитам.
НайтиПоРеквизитуи условие в запросе по неиндексированному полю заставляют СУБД перебирать всю таблицу. Нужно часто искать по реквизиту — поставьте ему свойствоИндексироватьв конфигураторе. - 🟡 Не читайте объект там, где хватает ссылки.
Выборка.ПолучитьОбъект()иСсылка.ПолучитьОбъект()поднимают из базы весь элемент с табличными частями. Если нужны одно-два поля, читайте их прямо через ссылку (Ссылка.Наименование) — это использует кэш и дешевле.
Отдельно о предопределённых элементах. Рубль, ставку «Без НДС», типовой вид контактной информации не ищут по коду или имени в коде — коды и имена могут поменяться. Берите их по внутреннему имени:
Рубль = ПредопределённоеЗначение("Справочник.Валюты.Рубль");
Так ссылка резолвится по метаданным, без запроса к данным, и не сломается при смене кода.
Сомневаетесь, что тормозит, — включите замер производительности или технологический журнал по событию DBMSSQL и посмотрите реальные запросы к СУБД. Гадать по коду на глаз дольше, чем снять замер.
Выбор значения справочника в форме
Если справочник выбирает пользователь, писать поиск вручную не нужно. Достаточно реквизита (объекта или формы) с типом СправочникСсылка.<Имя> — платформа сама покажет поле ввода с кнопкой выбора и выпадающим списком. Открывать и обрабатывать форму выбора вручную приходится редко.
Чаще нужно ограничить, что пользователь может выбрать. Для этого есть два штатных механизма.
- Отбор в параметрах выбора реквизита. Задаётся в конфигураторе (свойство
Параметры выбора) или программно черезПараметрыВыбораэлемента формы. Список сразу откроется уже отфильтрованным. - Связи параметров выбора — когда допустимые значения одного поля зависят от другого (договор зависит от контрагента). Настраиваются свойством
Связи параметров выбора.
Программно форму выбора открывают, когда нужен нестандартный сценарий:
ОткрытьФорму("Справочник.Номенклатура.ФормаВыбора", , Элемент);
Выбранное значение приходит в обработчик ОбработкаВыбора элемента формы. Там его и присваивают реквизиту.
Отбор в параметрах выбора — это удобство интерфейса, а не защита данных. Пользователь с правами может подставить значение и в обход формы. Критичные ограничения дублируйте проверкой при записи объекта.
Частые ошибки при поиске элементов справочника
Неопределено. НайтиПо... возвращает пустую ссылку, а не Неопределено. Проверяйте через .Пустая() или Не ЗначениеЗаполнено(...).ТочноеСоответствие — поиск идёт по началу строки и берёт первый совпавший. Ставьте Истина и помните: наименование не обязано быть уникальным.НайтиПо... или запрос стоят внутри цикла. Вынесите выборку данных за цикл: один запрос вместо тысячи.ПредопределённоеЗначение("Справочник.…").Проверьте себя
Частые вопросы
Чем `Пустая()` отличается от `ЗначениеЗаполнено()`?
Пустая() проверяет только, что ссылка не указывает на элемент. ЗначениеЗаполнено() — универсальная функция для значений любого типа, её удобнее применять в общей бизнес-логике. Для ссылки обе дадут одинаковый результат на «нашли / не нашли».Как найти элемент сразу по двум реквизитам?
НайтиПоРеквизиту умеет искать только по одному полю. По нескольким условиям — запрос с перечислением условий через И в секции ГДЕ.Почему нельзя искать предопределённый элемент по коду?
ПредопределённоеЗначение — нет.Выбрать() или запрос — что брать по умолчанию?
Выбрать() оправдан, когда нужно обойти справочник и для каждого элемента вызвать ПолучитьОбъект(), чтобы изменить и записать.Как в иерархическом справочнике получить все элементы группы вместе с подгруппами?
Ссылка В ИЕРАРХИИ (&Группа). В объектной модели — метод ВыбратьИерархически() с проверкой ЭтоГруппа, чтобы пропускать папки.