Разработка и Конфигурация · 12 мин
Как обойти справочник 1С в цикле: способы, примеры и ошибки
Как обойти справочник 1С в цикле: методы Выбрать(), запрос с обходом, иерархические справочники, отборы, типичные ошибки и примеры кода на встроенном языке.
Обход справочника в цикле — одна из базовых задач при доработке конфигураций 1С: заполнение реквизитов, поиск дублей, пакетная обработка элементов, формирование печатных форм. Во встроенном языке есть два принципиально разных подхода: перебор через объектную выборку методом Выбрать() и обход результата запроса. Первый проще для понимания, второй — быстрее и предпочтительнее в большинстве реальных сценариев.
Выбор способа влияет на производительность и нагрузку на сервер. Объектный обход читает каждый элемент целиком, включая все реквизиты и табличные части, тогда как запрос забирает из базы только нужные поля. На справочнике в несколько десятков тысяч элементов разница во времени выполнения может быть в десятки раз.
Ниже разобраны оба подхода с готовыми примерами кода, особенности работы с иерархическими справочниками, отборами, а также типичные ошибки, которые приводят к лишним обращениям к базе и блокировкам.
Способы обхода справочника: что выбрать
Существует три рабочих механизма перебора элементов справочника. Первый — выборка через метод Выбрать() менеджера справочника: он возвращает объект типа «СправочникВыборка», который последовательно читает элементы из базы. Второй — запрос на встроенном языке запросов с последующим обходом выборки из результата. Третий, реже используемый, — получение таблицы значений через Выгрузить() и обход её в цикле «Для Каждого».
Ключевое отличие объектной выборки от запроса — состав читаемых данных. Выборка методом Выбрать() поднимает из базы все поля элемента, даже если нужны только код и наименование. Запрос позволяет явно перечислить нужные поля, и платформа прочитает только их. Дополнительно запрос умеет сортировать, группировать и соединять справочник с другими таблицами без вложенных циклов.
Сравнение вариантов обхода
Подходит для небольших справочников и случаев, когда элементы нужно изменять. Читает все поля каждого элемента, медленнее на больших объёмах.
Оптимален для чтения и анализа данных. Читает только указанные поля, поддерживает отборы, сортировку и итоги. Не подходит для изменения элементов напрямую.
Удобен, когда данные нужны многократно в одном алгоритме или требуется произвольный доступ по индексу. Требует больше памяти: вся таблица хранится целиком.
Обход справочника методом Выбрать()
Объектная выборка создаётся вызовом метода Выбрать() у менеджера справочника. Без параметров метод вернёт все элементы, включая помеченные на удаление. Обход выполняется циклом Пока Выборка.Следующий() Цикл — метод Следующий() перемещает указатель на следующий элемент и возвращает Истина, пока элементы не закончились.
Выборка = Справочники.Номенклатура.Выбрать();
Пока Выборка.Следующий() Цикл
Если Выборка.ПометкаУдаления Тогда
Продолжить;
КонецЕсли;
Сообщить(Выборка.Код + " — " + Выборка.Наименование);
КонецЦикла;
Обратите внимание: внутри цикла переменная Выборка сама представляет текущий элемент, обращение к реквизитам идёт напрямую через точку. Получать объект методом ПолучитьОбъект() нужно только если элемент планируется изменять. Каждый вызов ПолучитьОбъект() — это дополнительное чтение из базы и создание объекта в памяти, поэтому без необходимости его не вызывают.
Выборка с отбором и порядком
Метод Выбрать() принимает три необязательных параметра: родитель, владелец и отбор. Отбор передаётся структурой, где ключ — имя реквизита, а значение — искомое значение. Это позволяет обойти не весь справочник, а только нужную часть, что существенно сокращает время выполнения.
Отбор = Новый Структура;
Отбор.Вставить("Поставщик", ПоставщикСсылка);
Выборка = Справочники.Договоры.Выбрать(, ВладелецКонтрагент, Отбор);
Пока Выборка.Следующий() Цикл
// обработка договоров конкретного контрагента
КонецЦикла;
Ограничение отбора через структуру — сравнение только на равенство. Условия вида «больше», «содержит подстроку», «в списке значений» таким способом не задать: для них нужен запрос.
Обход справочника через запрос
Запрос — основной способ обхода справочника в серверном коде обработок, отчётов и регламентных заданий. Текст запроса описывает, какие поля и с какими условиями нужны, а платформа выполняет чтение одним обращением к СУБД. Результат обходится методом Выбрать() у объекта «РезультатЗапроса» — синтаксис цикла тот же, что и у объектной выборки.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ПометкаУдаления
|
|УПОРЯДОЧИТЬ ПО
| Наименование";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Код + " — " + Выборка.Наименование);
КонецЦикла;
Параметры в запрос передаются через Запрос.УстановитьПараметр("Имя", Значение), а в тексте используются с амперсандом: ГДЕ Номенклатура.Родитель = &Родитель. Никогда не подставляйте пользовательский ввод в текст запроса конкатенацией строк — это создаёт риск ошибок и некорректной выборки.
Обход с группировкой и итогами
Если по иерархическому справочнику нужны итоги по группам, запрос позволяет получить их конструкцией ИТОГИ ПО, а обход выполняется с указанием типа обхода ПоГруппировкам. Внутри цикла по группам открывается вложенный цикл по детальным записям через Выборка.Выбрать(). Это стандартный приём для печатных форм с шапками групп.
ВыборкаГрупп = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГрупп.Следующий() Цикл
Детали = ВыборкаГрупп.Выбрать();
Пока Детали.Следующий() Цикл
// обработка элемента
КонецЦикла;
КонецЦикла;Обход иерархического справочника
Иерархические справочники (с видом иерархии «Список» или «Группы и элементы») требуют отдельного внимания: часто нужно обойти только элементы конкретной папки со всеми вложенными подпапками. Простой отбор Родитель = Папка вернёт лишь элементы первого уровня. Для обхода всей ветки в запросе используется оператор В ИЕРАРХИИ.
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Товары
|ГДЕ
| Товары.Родитель В ИЕРАРХИИ(&Папка)
| И НЕ Товары.ЭтоГруппа";
Условие НЕ Товары.ЭтоГруппа отсекает сами папки, если обрабатывать нужно только элементы. При объектном обходе аналогичный эффект достигается проверкой Если Выборка.ЭтоГруппа Тогда Продолжить; внутри цикла. Для справочников с видом иерархии «Элементы» поля ЭтоГруппа нет — там групп не существует в принципе.
- 1Определите ссылку на корневую папку ветки (Родитель нужного уровня)
- 2Сформируйте запрос с условием «Родитель В ИЕРАРХИИ(&Папка)»
- 3Добавьте отсечение групп через «НЕ ЭтоГруппа», если папки не нужны
- 4Выполните запрос и обойдите выборку циклом «Пока Выборка.Следующий()»
- 5Проверьте на тестовой папке, что количество обработанных элементов совпадает с ожидаемым
Изменение элементов внутри цикла
Когда задача — не прочитать, а изменить элементы (заполнить реквизит, перенести в другую группу), обход строится иначе. Сначала запросом или выборкой получают ссылки на нужные элементы, затем для каждой ссылки вызывают ПолучитьОбъект(), меняют реквизиты и вызывают Записать(). Пропуск ПолучитьОбъект() и попытка записать ссылку напрямую — частая ошибка новичков: у объекта «СправочникСсылка» методов записи нет.
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ЭлементОбъект = Выборка.Ссылка.ПолучитьОбъект();
ЭлементОбъект.СтавкаНДС = Перечисления.СтавкиНДС.НДС20;
ЭлементОбъект.Записать();
КонецЦикла;
При пакетной обработке большого числа элементов запись стоит выполнять в транзакции с фиксацией порциями или хотя бы с обработкой исключений: если одна запись упадёт из-за блокировки или проверки заполнения, остальные не должны потеряться. Перед массовой обработкой в рабочей базе обязательно сделайте резервную копию или сначала прогоните код на копии базы.
Производительность и типичные ошибки
Самая дорогая ошибка — запрос внутри цикла. Если для каждого элемента справочника выполняется отдельный запрос к другой таблице (например, поиск цены или остатка), число обращений к СУБД равно числу элементов, и время работы растёт линейно. Правильный подход — один запрос с соединением таблиц, результат которого обходится одним циклом.
Вторая группа проблем связана с лишними данными: чтение всех полей через объектную выборку, когда нужны два реквизита, и получение объекта там, где достаточно ссылки. Третья — обход без учёта пометок удаления и предопределённых элементов, из-за чего в обработку попадают «мусорные» записи.
Как проверить результат обхода
Совпадает с числом элементов, возвращаемых тем же запросом в консоли запросов
Меньше ожидаемого — проверьте отборы и условие ПометкаУдаления
Обход тысяч элементов занимает секунды при чтении через запрос
Минуты на небольшом справочнике — вероятно, запрос внутри цикла или ПолучитьОбъект() без нужды
Реквизиты заполнены у всех целевых элементов, случайные записи не затронуты
Изменения частичные или затронуты лишние элементы — ошибка в условии отбора
Часто задаваемые вопросы
Чем отличается СправочникВыборка от СправочникОбъект?
Выборка — это лёгкий курсор для последовательного чтения данных элемента: реквизиты доступны, но изменить и записать элемент через неё нельзя. Объект — полноценное представление элемента в памяти с методами Записать() и УстановитьПометкуУдаления(). Объект создаётся вызовом ПолучитьОбъект() у ссылки и требуется только для модификации данных.
Как обойти справочник, отсортировав по наименованию?
В объектной выборке сортировка задаётся четвёртым параметром метода Выбрать() в виде строки, например Выбрать(, , , "Наименование Возр"). В запросе используется секция УПОРЯДОЧИТЬ ПО Наименование. Запрос гибче: позволяет сортировать по нескольким полям и по полям связанных таблиц.
Почему цикл обходит не все элементы справочника?
Частые причины: задан отбор в методе Выбрать(), условие в запросе отсекает часть записей, либо внутри цикла стоит Продолжить для групп или помеченных на удаление. Проверьте результат тем же запросом в консоли запросов и сравните количество строк с фактическим числом итераций.
Можно ли прервать обход досрочно?
Да, оператор Прервать завершает цикл в любой момент, например когда нужный элемент найден. Для выборки из запроса это безопасно. Если обход выполнялся внутри транзакции с изменением данных, перед прерыванием убедитесь, что зафиксированы или отменены нужные изменения.
Что быстрее: Выбрать() или запрос?
На чтение почти всегда быстрее запрос, особенно если выбираются только нужные поля и есть отбор по индексированным реквизитам. Объектная выборка конкурентна лишь на маленьких справочниках, когда нужно читать элемент целиком. На десятках тысяч элементов разница в пользу запроса становится многократной.
Как обойти табличную часть каждого элемента справочника?
Через объектную выборку табличная часть доступна сразу: внутри цикла обращайтесь к ней как к коллекции — Для Каждого Строка Из Выборка.Товары Цикл. В запросе табличная часть выбирается отдельным источником вида Справочник.Номенклатура.Товары, обычно с соединением к шапке по ссылке.
Что важно запомнить
Для чтения данных используйте запрос с явным списком полей, для изменения — выборку ссылок с последующим ПолучитьОбъект() и Записать(). Не выполняйте запросы и НайтиПо* внутри цикла, учитывайте пометки удаления и иерархию, а массовые изменения проверяйте сначала на копии базы.