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