Работа с данными в платформе 1С:Предприятие неразрывно связана с механизмом выборки информации из базы данных. Когда программист формирует запрос, он получает на выходе объект ВыборкаИзРезультатаЗапроса. Именно с этим объектом предстоит взаимодействовать для дальнейшей обработки полученных сведений.

Понимание того, как обойти результат запроса 1С, является фундаментальным навыком для любого разработчика. От выбора правильного способа итерации зависит не только читаемость кода, но и производительность всего приложения в целом.

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

Базовый цикл обхода строк выборки

Самый распространенный и интуитивно понятный способ обработки данных — это использование оператора Пока.. Цикл. Этот метод позволяет последовательно перебирать каждую строку, полученную в результате выполнения запроса, и выполнять над ней необходимые действия.

Для начала работы необходимо вызвать метод Выбрать() у объекта результата запроса. Этот метод возвращает объект выборки, который позиционируется на первой строке. Если выборка пуста, метод вернет ложь, и цикл не выполнится ни разу.

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

Рассмотрим классический пример обхода списка номенклатуры с ценами. Здесь мы выводим данные в консоль или формируем отчет.

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

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

"ВЫБРАТЬ

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

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

| РегистрСведений.ЦеныНоменклатуры.Цена КАК Цена

|ИЗ

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

| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры

| ПО Номенклатура.Ссылка = ЦеныНоменклатуры.Номенклатура";

Результат = Запрос.Выполнить();

Выборка = Результат.Выбрать();

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

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

КонецЦикла;

Важно отметить, что метод Следующий() перемещает курсор выборки на следующую строку и возвращает истину, если такая строка существует. Это стандартный паттерн, который используется в 90% случаев обработки плоских списков данных.

⚠️ Внимание: Никогда не пытайтесь изменять данные в базе напрямую внутри цикла обхода выборки запроса без использования менеджеров объектов или специальных механизмов записи. Это может привести к блокировкам и конфликтам транзакций.

📊 Какой способ обхода вы используете чаще всего?
Цикл Пока..Следующий
ForEach
Обход итогов
Ручное управление курсором

Использование оператора Для Каждого

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

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

Этот подход особенно удобен, когда внутри цикла не требуется сложная логика управления потоком выполнения, например, преждевременный выход или пропуск итераций на основе сложных условий состояния курсора.

Синтаксис оператора предельно прост: указывается переменная-приемник и коллекция, которую нужно обойти. В контексте запроса коллекцией является объект выборки.

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

Для Каждого ТекущаяСтрока Из ВыборкаДанных Цикл

// Обработка текущей строки

Если ТекущаяСтрока.Цена > 1000 Тогда

Сообщить("Дорогой товар: " + ТекущаяСтрока.Наименование);

КонецЕсли;

КонецЦикла;

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

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

💡

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

Обработка итогов и группировок в запросе

Часто возникает ситуация, когда данные в запросе сгруппированы, и к ним добавлены итоги с помощью ключевого слова ИТОГИ. В этом случае структура выборки усложняется: появляются строки, содержащие агрегированные данные, а не детальные записи.

Чтобы корректно обойти результат запроса 1С с итогами, необходимо уметь различать обычные строки и строки итогов. Для этого в выборке появляются специальные служебные поля, начинающиеся с символа подчеркивания, например, _ИтогиПоГруппировке.

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

Рассмотрим пример, где мы группируем продажи по менеджерам и выводим общую сумму.

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

"ВЫБРАТЬ

| Продажи.Менеджер,

| СУММА(Продажи.Сумма) КАК СуммаПродаж

|ИТОГИ ПО Менеджер

|ИЗ

| Документ.РеализацияТоваровУслуг.Товары КАК Продажи

|ГДЕ

| Продажи.Период МЕЖДУ &НачПериода И &КонПериода";

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

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

Если Выборка._ИтогиПоМенеджер Тогда

Сообщить("--- ИТОГО по менеджеру: " + Выборка.Менеджер + " ---");

Сообщить("Общая сумма: " + Выборка.СуммаПродаж);

Иначе

// Это детальная запись (если бы мы не группировали полностью)

// В данном примере с ПО Менеджер все строки будут итоговыми по менеджеру

// Но если бы было ПО Менеджер, Товар, то были бы и детали

КонецЕсли;

КонецЦикла;

При работе с многоуровневыми итогами, например ИТОГИ ПО Менеджер, Товар, система генерирует поля _ИтогиПоМенеджер и _ИтогиПоТовар. Комбинация этих флагов позволяет понять уровень вложенности текущей строки в иерархии группировок.

Тип строки Поле _ИтогиПоМенеджер Поле _ИтогиПоТовар Описание
Детальная запись Ложь Ложь Конкретная продажа конкретного товара
Итог по товару Ложь Истина Сумма продаж одного товара у менеджера
Итог по менеджеру Истина Истина Общая сумма всех продаж менеджера
Общий итог Истина Истина Сумма по всей выборке (если есть ИТОГИ ОБЩИЕ)

⚠️ Внимание: Поля итогов (_ИтогиПо..) появляются в выборке автоматически только если в запросе явно указано ключевое слово ИТОГИ. Без него эти поля будут отсутствовать, и обращение к ним вызовет ошибку.

Что делать, если нужно скрыть итоги в выводе?

Если вам нужны данные для расчетов, но вы не хотите выводить строки итогов в отчет, просто добавьте проверку "Если Не Выборка._ИтогиПо.. Тогда" внутри цикла и обрабатывайте только детальные записи.

Работа с иерархическими данными и обход дерева

Справочники в 1С часто имеют иерархическую структуру. При выборке таких данных с опцией ИЕРАРХИЯ, результат запроса содержит не просто плоский список, а дерево элементов. Стандартный цикл Следующий() проходит по этому дереву в порядке обхода (обычно префиксном).

Для визуального отображения вложенности в выборке появляется специальное поле _УровеньИерархии. Оно содержит числовое значение, указывающее на глубину вложенности текущего элемента относительно корня выборки.

Используя это поле, можно формировать отступы при выводе данных, создавая эффект древовидной структуры в текстовом отчете или консольном отладчике. Это критически важно для восприятия информации пользователем.

Кроме того, существует метод СледующийУзел(), который позволяет перемещаться по дереву более осмысленно, пропуская целые ветви, если они не нужны для текущей логики обработки. Это может существенно ускорить работу с огромными справочниками.

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

"ВЫБРАТЬ

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

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

|ИЗ

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

|ГДЕ

| Номенклатура.ЭтоГруппа = ИСТИНА

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

| Номенклатура.Родитель,

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

|ИЕРАРХИЯ";

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

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

Отступ = СтрПовтор(" ", Выборка._УровеньИерархии);

Сообщить(Отступ + Выборка.Наименование);

КонецЦикла;

При использовании СледующийУзел() Это позволяет реализовать логику "свернуть/развернуть" при программном обходе.

☑️ Проверка иерархического запроса

Выполнено: 0 / 4

Обход результатов с несколькими таблицами вывода

Запрос в 1С может возвращать не один, а несколько наборов данных одновременно. Это реализуется через объединение запросов или использование специальных конструкций, формирующих несколько таблиц вывода в одном результате выполнения.

Объект РезультатЗапроса в таком случае содержит коллекцию таблиц. Чтобы получить доступ ко второй или третьей таблице, необходимо использовать метод ВыбратьТаблицуРезультата(ИмяТаблицы) или ВыбратьТаблицуРезультата(НомерТаблицы).

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

Алгоритм обработки выглядит следующим образом: сначала выбирается первая таблица по умолчанию, обрабатывается, затем явно переключаются на вторую таблицу и повторяют цикл обхода.

Результат = Запрос.Выполнить();

// Обработка первой таблицы (Детальные записи)

ВыборкаДетали = Результат.Выбрать();

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

// Логика обработки деталей

КонецЦикла;

// Переключение на вторую таблицу (Итоги)

ВыборкаИтоги = Результат.ВыбратьТаблицуРезультата("Итоги");

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

// Логика обработки итогов

КонецЦикла;

Важно давать понятные имена таблицам результатов в тексте запроса, если вы используете конструкцию ВЫБРАТЬ.. ПОМЕСТИТЬ внутри одного скрипта или явное именование наборов. Это упрощает поддержку кода в будущем.

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

💡

Использование одного запроса с несколькими таблицами вывода может ускорить получение данных до 30% за счет сокращения количества обращений к серверу баз данных.

Оптимизация и типичные ошибки при обходе

Даже зная синтаксис, разработчики часто совершают ошибки, которые приводят к падению производительности. Самая главная ошибка — выполнение дополнительных запросов внутри цикла обхода. Это явление известно как "проблема N+1 запроса".

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

Вместо этого следует использовать временные таблицы или дополнительные соединения (ЛЕВОЕ СОЕДИНЕНИЕ) в основном запросе, чтобы забрать все необходимые данные одним пакетом. Это золотое правило оптимизации в 1С.

Еще одна частая ошибка — игнорирование индексации полей, по которым происходит соединение или отбор. Если в цикле вы планируете делать поиск по полученным данным (например, через НайтиПоРеквизиту), убедитесь, что соответствующие индексы существуют.

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

Как отладить медленный обход?

Используйте технологический журнал (ТЖ) 1С. Включите логирование длительных запросов (LongQuery) и посмотрите, сколько времени тратится на выполнение основного запроса и есть ли запросы внутри цикла.

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

В чем разница между Выборка.Следующий() и Выборка.СледующийУзел()?

Метод Следующий() просто перемещает курсор на следующую физическую строку в результате выборки. Метод СледующийУзел() учитывает иерархическую структуру данных и позволяет перемещаться по логическим узлам дерева, что удобно при пропуске целых ветвей справочника.

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

Нет, объект ВыборкаИзРезультатаЗапроса доступен только для чтения. Для изменения данных необходимо получить ссылку на объект (Документ, Справочник) из поля выборки и использовать методы менеджера объекта или записать объект в режиме записи.

Что делать, если запрос возвращает пустую выборку?

Метод Выбрать() вернет объект выборки, но первый вызов Следующий() вернет Ложь. Цикл просто не выполнится ни разу. Ошибки не возникнет, но полезно добавить проверку, если от пустого результата зависит дальнейшая логика программы.

Как получить количество строк в результате запроса без полного обхода?

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

Влияет ли порядок полей в запросе на скорость обхода?

Сам порядок полей в списке выбора не влияет на скорость итерации цикла. Однако порядок полей в условиях соединения и отбора (ГДЕ) критически важен для использования индексов СУБД, что напрямую влияет на скорость формирования самой выборки.