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

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

Как обойти элементы справочника в 1С: выборка, запрос и иерархия

Способы обхода элементов справочника в 1С: выборка через Справочники.Выбрать(), запрос, обход иерархии и групп. Примеры кода, ошибки и проверка результата.

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

Выбор способа влияет на скорость выполнения, объём потребляемой памяти и безопасность изменения данных. Объектная выборка проще в написании, но медленнее на больших объёмах, потому что каждый элемент читается из базы отдельно. Запрос получает данные одним обращением к СУБД, но по умолчанию возвращает только выбранные поля, и для изменения элементов потребуется дополнительное обращение к объектам.

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

Обход через объектную выборку: метод Выбрать()

Самый простой способ перебрать элементы справочника — получить выборку методом Выбрать() менеджера справочника и пройти её в цикле. Выборка поддерживает отбор по родителю, владельцу и произвольному отбору, а также сортировку.

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

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

Если Выборка.ЭтоГруппа Тогда

Продолжить;

КонецЕсли;

Сообщить("Элемент: " + Выборка.Наименование);

КонецЦикла;

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

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

Отбор = Новый Структура("ПометкаУдаления", Ложь);

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

Обход через запрос: быстрее и гибче

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

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

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

"ВЫБРАТЬ

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

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

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

|ИЗ

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

|ГДЕ

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

| И НЕ Номенклатура.ЭтоГруппа";

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

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

Сообщить(Выборка.Наименование + " (" + Выборка.Артикул + ")");

КонецЦикла;

Обратите внимание: выборка запроса содержит только те поля, которые перечислены в секции ВЫБРАТЬ. Если внутри цикла потребуется изменить элемент, придётся вызвать Выборка.Ссылка.ПолучитьОбъект() — и преимущество в скорости частично теряется. Поэтому для массового изменения данных часто комбинируют подходы: запросом отбирают ссылки, затем обрабатывают объекты порциями.

Сравнение способов обхода

Выборка через Выбрать()

Простой код, удобен для изменения элементов. Медленный на больших объёмах, реквизиты недоступны без ПолучитьОбъект().

Запрос

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

Запрос + пакетная обработка объектов

Оптимален для массовых изменений: запрос отбирает ссылки, объекты обрабатываются порциями в транзакции.

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

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

Обход результата запроса с иерархией

Если в запросе указать ИТОГИ ПО Ссылка ТОЛЬКО ИЕРАРХИЯ, результат можно обойти как дерево: метод Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией) возвращает выборку, в которой доступны вложенные строки через рекурсивный вызов той же процедуры.

Процедура ОбойтиУровень(Выборка, Уровень)

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

Сообщить(СтрПовторить(" ", Уровень) + Выборка.Наименование);

Вложенная = Выборка.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией);

ОбойтиУровень(Вложенная, Уровень + 1);

КонецЦикла;

КонецПроцедуры

Отбор элементов внутри группы с учётом вложенности

Чтобы получить все элементы группы включая вложенные подгруппы, в запросе используют условие Родитель В ИЕРАРХИИ(&Группа). Оператор В ИЕРАРХИИ работает только для иерархических справочников и учитывает всю цепочку вложенности, что исключает ручную рекурсию по группам.

Изменение элементов при обходе

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

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

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

"ВЫБРАТЬ

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

|ИЗ

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

|ГДЕ

| НЕ Номенклатура.ЭтоГруппа

| И Номенклатура.СтавкаНДС = &ПустаяСтавка";

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

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

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

ЭлементОбъект.СтавкаНДС = НоваяСтавка;

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

КонецЦикла;

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

Производительность и ограничения

Главная ошибка при обходе — обращение к реквизитам через точку от ссылки внутри цикла. Конструкция вида Выборка.Ссылка.Артикул приводит к отдельному запросу к базе на каждой итерации. Правильный подход — выбрать все нужные поля сразу в тексте запроса.

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

Для очень больших справочников (сотни тысяч элементов) имеет смысл обрабатывать данные порциями: например, выбирать первые 1000 записей (ВЫБРАТЬ ПЕРВЫЕ 1000) с условием по ссылке больше последней обработанной. Это снижает пиковое потребление памяти и позволяет выводить прогресс выполнения.

Типичные ошибки
01
Обращение к реквизитам через Ссылка.Реквизит внутри цикла — запрос к базе на каждой итерации и резкое падение скорости.
02
Фильтрация элементов условием Если внутри цикла вместо отбора в запросе — лишний обход тысяч ненужных записей.
03
Запись объектов без транзакции при массовом изменении — при сбое часть данных останется изменённой, часть нет.
04
Игнорирование служебных групп: без проверки ЭтоГруппа обработка пытается изменить группы как обычные элементы.
05
Изменение состава выборки прямо во время обхода без повторного выполнения запроса — часть элементов пропускается.

Проверка результата обхода

После выполнения обхода убедитесь, что обработано ожидаемое количество элементов. Простой способ — счётчик внутри цикла и сравнение его с количеством записей, которое возвращает тот же запрос с функцией КОЛИЧЕСТВО(*). Расхождение означает, что часть элементов отфильтрована условиями или пропущена из-за ошибок записи.

Для обработок, изменяющих данные, полезно вести журнал: записывать в регистр сведений или текстовый лог ссылку на элемент, выполненное действие и результат. Это позволяет восстановить картину при сбое и доказать, какие элементы были затронуты.

Контрольные точки

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

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

Счётчик цикла совпадает с результатом запроса КОЛИЧЕСТВО по тем же условиям

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

Счётчик меньше — часть элементов пропущена или отфильтрована неожиданно

Значения изменённых реквизитов
Норма

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

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

У части элементов старые значения — запись не выполнилась, ищите ошибки в логе

Группы справочника
Норма

Группы исключены из обработки проверкой ЭтоГруппа

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

Обработка пыталась изменить группы — возможны ошибки записи

Пошаговый порядок для типовой задачи обхода

Типовая задача «пройти все элементы и выполнить действие» решается по устойчивой схеме независимо от конкретного справочника. Следование шагам снижает риск пропустить элементы или повредить данные.

Порядок действий
  1. 1
    Определите цель: только чтение данных или изменение элементов — от этого зависит выбор между запросом и объектной выборкой.
  2. 2
    Сформулируйте отбор и перенесите его в текст запроса или параметры метода Выбрать(), исключив группы и помеченные на удаление, если они не нужны.
  3. 3
    Напишите цикл Пока Выборка.Следующий() Цикл и поместите в него только необходимую логику без обращений к базе через точку.
  4. 4
    Для изменения данных добавьте транзакцию, получение объекта через ПолучитьОбъект() и обработку ошибок записи.
  5. 5
    Протестируйте обход на копии базы в режиме чтения: выведите список затрагиваемых элементов и сверьте их количество.
  6. 6
    Запустите обработку на рабочей базе и проверьте результат контрольным запросом.
Итог

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

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

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

Чем выборка отличается от результата запроса?

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

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

В запросе добавьте условие НЕ Справочник.ЭтоГруппа, а при объектной выборке — проверку Если Выборка.ЭтоГруппа Тогда Продолжить; внутри цикла. Условие в запросе предпочтительнее, поскольку группы вообще не попадут в выборку.

Как обойти подчинённый справочник по владельцу?

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

Почему обход работает медленно и как ускорить?

Чаще всего причина — обращение к реквизитам через точку от ссылки внутри цикла, что порождает отдельный запрос к базе на каждой итерации. Вынесите все нужные поля в текст запроса. Дополнительно убедитесь, что отбор выполняется в запросе, а не условием в цикле, и что по полям отбора есть индексы.

Можно ли изменять элементы прямо во время обхода выборки?

Да, но с осторожностью. Изменение и запись объектов во время обхода допустимы, однако если запись меняет поля, входящие в отбор или сортировку выборки, поведение может стать непредсказуемым. Надёжная схема — сначала запросом собрать массив ссылок, затем обработать объекты по этому массиву.

Как обойти элементы всех подгрупп заданной группы?

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