К статье
Практический материал

Разработка и Конфигурация · 12 мин

Как обойти справочник 1С в цикле: способы, примеры и ошибки

Как обойти справочник 1С в цикле: методы Выбрать(), запрос с обходом, иерархические справочники, отборы, типичные ошибки и примеры кода на встроенном языке.

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

Выбор способа влияет на производительность и нагрузку на сервер. Объектный обход читает каждый элемент целиком, включая все реквизиты и табличные части, тогда как запрос забирает из базы только нужные поля. На справочнике в несколько десятков тысяч элементов разница во времени выполнения может быть в десятки раз.

Ниже разобраны оба подхода с готовыми примерами кода, особенности работы с иерархическими справочниками, отборами, а также типичные ошибки, которые приводят к лишним обращениям к базе и блокировкам.

Способы обхода справочника: что выбрать

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

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

Сравнение вариантов обхода

Метод Выбрать() (объектная выборка)

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

Запрос с обходом результата

Оптимален для чтения и анализа данных. Читает только указанные поля, поддерживает отборы, сортировку и итоги. Не подходит для изменения элементов напрямую.

Выгрузить() в таблицу значений

Удобен, когда данные нужны многократно в одном алгоритме или требуется произвольный доступ по индексу. Требует больше памяти: вся таблица хранится целиком.

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

Объектная выборка создаётся вызовом метода Выбрать() у менеджера справочника. Без параметров метод вернёт все элементы, включая помеченные на удаление. Обход выполняется циклом Пока Выборка.Следующий() Цикл — метод Следующий() перемещает указатель на следующий элемент и возвращает Истина, пока элементы не закончились.

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

Пока Выборка.Следующий() Цикл

Если Выборка.ПометкаУдаления Тогда

Продолжить;

КонецЕсли;

Сообщить(Выборка.Код + " — " + Выборка.Наименование);

КонецЦикла;

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

Выборка с отбором и порядком

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

Отбор = Новый Структура;

Отбор.Вставить("Поставщик", ПоставщикСсылка);

Выборка = Справочники.Договоры.Выбрать(, ВладелецКонтрагент, Отбор);

Пока Выборка.Следующий() Цикл

// обработка договоров конкретного контрагента

КонецЦикла;

Ограничение отбора через структуру — сравнение только на равенство. Условия вида «больше», «содержит подстроку», «в списке значений» таким способом не задать: для них нужен запрос.

Обход справочника через запрос

Запрос — основной способ обхода справочника в серверном коде обработок, отчётов и регламентных заданий. Текст запроса описывает, какие поля и с какими условиями нужны, а платформа выполняет чтение одним обращением к СУБД. Результат обходится методом Выбрать() у объекта «РезультатЗапроса» — синтаксис цикла тот же, что и у объектной выборки.

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Номенклатура.Ссылка КАК Ссылка,

| Номенклатура.Код КАК Код,

| Номенклатура.Наименование КАК Наименование

|ИЗ

| Справочник.Номенклатура КАК Номенклатура

|ГДЕ

| НЕ Номенклатура.ПометкаУдаления

|

|УПОРЯДОЧИТЬ ПО

| Наименование";

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл

Сообщить(Выборка.Код + " — " + Выборка.Наименование);

КонецЦикла;

Параметры в запрос передаются через Запрос.УстановитьПараметр("Имя", Значение), а в тексте используются с амперсандом: ГДЕ Номенклатура.Родитель = &Родитель. Никогда не подставляйте пользовательский ввод в текст запроса конкатенацией строк — это создаёт риск ошибок и некорректной выборки.

Обход с группировкой и итогами

Если по иерархическому справочнику нужны итоги по группам, запрос позволяет получить их конструкцией ИТОГИ ПО, а обход выполняется с указанием типа обхода ПоГруппировкам. Внутри цикла по группам открывается вложенный цикл по детальным записям через Выборка.Выбрать(). Это стандартный приём для печатных форм с шапками групп.

bsl
ВыборкаГрупп = Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГрупп.Следующий() Цикл
    Детали = ВыборкаГрупп.Выбрать();
    Пока Детали.Следующий() Цикл
        // обработка элемента
    КонецЦикла;
КонецЦикла;

Обход иерархического справочника

Иерархические справочники (с видом иерархии «Список» или «Группы и элементы») требуют отдельного внимания: часто нужно обойти только элементы конкретной папки со всеми вложенными подпапками. Простой отбор Родитель = Папка вернёт лишь элементы первого уровня. Для обхода всей ветки в запросе используется оператор В ИЕРАРХИИ.

Запрос.Текст =

"ВЫБРАТЬ

| Товары.Ссылка КАК Ссылка

|ИЗ

| Справочник.Номенклатура КАК Товары

|ГДЕ

| Товары.Родитель В ИЕРАРХИИ(&Папка)

| И НЕ Товары.ЭтоГруппа";

Условие НЕ Товары.ЭтоГруппа отсекает сами папки, если обрабатывать нужно только элементы. При объектном обходе аналогичный эффект достигается проверкой Если Выборка.ЭтоГруппа Тогда Продолжить; внутри цикла. Для справочников с видом иерархии «Элементы» поля ЭтоГруппа нет — там групп не существует в принципе.

Порядок действий при обходе ветки иерархии
  1. 1
    Определите ссылку на корневую папку ветки (Родитель нужного уровня)
  2. 2
    Сформируйте запрос с условием «Родитель В ИЕРАРХИИ(&Папка)»
  3. 3
    Добавьте отсечение групп через «НЕ ЭтоГруппа», если папки не нужны
  4. 4
    Выполните запрос и обойдите выборку циклом «Пока Выборка.Следующий()»
  5. 5
    Проверьте на тестовой папке, что количество обработанных элементов совпадает с ожидаемым

Изменение элементов внутри цикла

Когда задача — не прочитать, а изменить элементы (заполнить реквизит, перенести в другую группу), обход строится иначе. Сначала запросом или выборкой получают ссылки на нужные элементы, затем для каждой ссылки вызывают ПолучитьОбъект(), меняют реквизиты и вызывают Записать(). Пропуск ПолучитьОбъект() и попытка записать ссылку напрямую — частая ошибка новичков: у объекта «СправочникСсылка» методов записи нет.

Выборка = Запрос.Выполнить().Выбрать();

Пока Выборка.Следующий() Цикл

ЭлементОбъект = Выборка.Ссылка.ПолучитьОбъект();

ЭлементОбъект.СтавкаНДС = Перечисления.СтавкиНДС.НДС20;

ЭлементОбъект.Записать();

КонецЦикла;

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

Производительность и типичные ошибки

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

Вторая группа проблем связана с лишними данными: чтение всех полей через объектную выборку, когда нужны два реквизита, и получение объекта там, где достаточно ссылки. Третья — обход без учёта пометок удаления и предопределённых элементов, из-за чего в обработку попадают «мусорные» записи.

Типичные ошибки при обходе справочника
01
Запрос внутри цикла — обращение к базе на каждый элемент; заменяется одним запросом с соединением таблиц
02
Вызов ПолучитьОбъект() для чтения данных — лишнее создание объекта; для чтения достаточно выборки
03
Отсутствие отбора по ПометкаУдаления — в обработку попадают удалённые элементы
04
Изменение элементов без транзакции и обработки исключений — частично выполненная обработка при сбое
05
Использование НайтиПоНаименованию() внутри цикла вместо соединения в запросе
Контрольные точки

Как проверить результат обхода

Количество обработанных записей
Норма

Совпадает с числом элементов, возвращаемых тем же запросом в консоли запросов

Тревожный признак

Меньше ожидаемого — проверьте отборы и условие ПометкаУдаления

Время выполнения
Норма

Обход тысяч элементов занимает секунды при чтении через запрос

Тревожный признак

Минуты на небольшом справочнике — вероятно, запрос внутри цикла или ПолучитьОбъект() без нужды

Данные после изменения
Норма

Реквизиты заполнены у всех целевых элементов, случайные записи не затронуты

Тревожный признак

Изменения частичные или затронуты лишние элементы — ошибка в условии отбора

Часто задаваемые вопросы

Чем отличается СправочникВыборка от СправочникОбъект?

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

Как обойти справочник, отсортировав по наименованию?

В объектной выборке сортировка задаётся четвёртым параметром метода Выбрать() в виде строки, например Выбрать(, , , "Наименование Возр"). В запросе используется секция УПОРЯДОЧИТЬ ПО Наименование. Запрос гибче: позволяет сортировать по нескольким полям и по полям связанных таблиц.

Почему цикл обходит не все элементы справочника?

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

Можно ли прервать обход досрочно?

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

Что быстрее: Выбрать() или запрос?

На чтение почти всегда быстрее запрос, особенно если выбираются только нужные поля и есть отбор по индексированным реквизитам. Объектная выборка конкурентна лишь на маленьких справочниках, когда нужно читать элемент целиком. На десятках тысяч элементов разница в пользу запроса становится многократной.

Как обойти табличную часть каждого элемента справочника?

Через объектную выборку табличная часть доступна сразу: внутри цикла обращайтесь к ней как к коллекции — Для Каждого Строка Из Выборка.Товары Цикл. В запросе табличная часть выбирается отдельным источником вида Справочник.Номенклатура.Товары, обычно с соединением к шапке по ссылке.

Итог

Что важно запомнить

Для чтения данных используйте запрос с явным списком полей, для изменения — выборку ссылок с последующим ПолучитьОбъект() и Записать(). Не выполняйте запросы и НайтиПо* внутри цикла, учитывайте пометки удаления и иерархию, а массовые изменения проверяйте сначала на копии базы.