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

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

Как обойти таблицу значений в цикле 1С

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

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

Язык 1С предлагает несколько способов обхода таблицы значений: классический цикл Для Каждого ... Из ... Цикл, цикл со счётчиком по индексам, выборку через НайтиСтроки и комбинированные варианты. Выбор способа влияет не только на читаемость кода, но и на производительность: неправильно организованный перебор на большой таблице может растянуть выполнение обработки с секунд до минут.

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

Базовый обход: цикл «Для Каждого»

Самый читаемый и рекомендуемый вариант — конструкция Для Каждого ... Из ... Цикл. Она перебирает строки таблицы последовательно, и внутри цикла доступна текущая строка со всеми её колонками:

Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл

Сообщить("Номенклатура: " + СтрокаТЗ.Номенклатура

+ ", количество: " + СтрокаТЗ.Количество);

КонецЦикла;

Обращение к колонкам идёт через точку: СтрокаТЗ.ИмяКолонки. Имя переменной цикла (СтрокаТЗ) произвольное — выбирайте осмысленное, особенно при вложенных циклах, чтобы не путать строки разных таблиц.

Внутри такого цикла можно не только читать, но и менять значения строки: присваивание СтрокаТЗ.Цена = НоваяЦена; изменит данные прямо в таблице, поскольку переменная цикла содержит ссылку на строку, а не её копию.

Для досрочного выхода используйте Прервать, для пропуска текущей строки — Продолжить. Типичный пример с пропуском:

Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл

Если СтрокаТЗ.Количество = 0 Тогда

Продолжить; // строки без количества пропускаем

КонецЕсли;

// обработка строки

КонецЦикла;

Обход по индексу: когда нужен номер строки

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

// Вариант 1: через Количество()

Для Индекс = 0 По ТаблицаЗначений.Количество() - 1 Цикл

СтрокаТЗ = ТаблицаЗначений[Индекс];

Сообщить("Строка №" + (Индекс + 1) + ": " + СтрокаТЗ.Наименование);

КонецЦикла;

// Вариант 2: через ВГраница()

Для Индекс = 0 По ТаблицаЗначений.ВГраница() Цикл

СтрокаТЗ = ТаблицаЗначений[Индекс];

// ...

КонецЦикла;

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

Типичная ситуация, где обход по индексу обязателен, — удаление строк во время перебора. Чтобы индексы не «съехали», идите с конца таблицы:

КоличествоСтрок = ТаблицаЗначений.Количество();

Для Инд = 1 По КоличествоСтрок Цикл

СтрокаТЗ = ТаблицаЗначений[КоличествоСтрок - Инд];

Если СтрокаТЗ.ПометитьНаУдаление Тогда

ТаблицаЗначений.Удалить(СтрокаТЗ);

КонецЕсли;

КонецЦикла;

Обход только нужных строк: НайтиСтроки вместо полного перебора

Частая задача — обработать не все строки, а только те, что удовлетворяют условию. Перебирать всю таблицу с проверкой Если внутри — рабочий, но не самый эффективный путь. Метод НайтиСтроки возвращает массив строк по отбору, и обходить можно только его:

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

ПараметрыОтбора.Вставить("Склад", СкладОтгрузки);

ПараметрыОтбора.Вставить("Отгружено", Ложь);

НайденныеСтроки = ТаблицаЗначений.НайтиСтроки(ПараметрыОтбора);

Для Каждого СтрокаТЗ Из НайденныеСтроки Цикл

// обрабатываем только строки нужного склада

КонецЦикла;

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

Дополнительный плюс: если в таблице есть индекс по полям отбора (создаётся методом ТаблицаЗначений.Индексы.Добавить("Склад")), поиск выполняется значительно быстрее. На таблицах в десятки тысяч строк разница между перебором с условием и поиском по индексу становится заметной на глаз.

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

Для Каждого

Универсальный вариант для чтения и изменения строк. Не подходит для удаления строк внутри цикла и не даёт индекс.

Цикл по индексу

Нужен, когда важен номер строки или таблица меняется во время обхода. Чуть более громоздкий синтаксис.

НайтиСтроки + обход массива

Оптимален для обработки строк по отбору на равенство, особенно с индексом. Не поддерживает сложные условия.

Запрос к таблице

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

Обход табличной части документа и формы

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

Табличная часть документа перебирается напрямую, и внутри цикла доступны как реквизиты строки, так и возможность их изменять:

Для Каждого СтрокаТЧ Из ДокументОбъект.Товары Цикл

Если СтрокаТЧ.Цена = 0 Тогда

СтрокаТЧ.Цена = ПолучитьЦену(СтрокаТЧ.Номенклатура);

КонецЕсли;

КонецЦикла;

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

Если нужно перенести данные из таблицы значений в табличную часть, не обязательно перебирать строки вручную — чаще достаточно Объект.Товары.Загрузить(ТаблицаЗначений). Ручной обход оправдан, только когда при копировании нужна дополнительная логика: проверки, преобразования, поиск соответствий.

Порядок выбора способа обхода
  1. 1
    Определите, нужно ли менять состав таблицы (удалять/добавлять строки) во время обхода
  2. 2
    Если нужен только просмотр и изменение значений — берите «Для Каждого»
  3. 3
    Если нужен номер строки или удаление — цикл по индексу, удаление с конца таблицы
  4. 4
    Если обрабатываются только строки по отбору на равенство — НайтиСтроки, при больших объёмах добавьте индекс
  5. 5
    Проверьте результат на копии данных: количество обработанных строк и итоговые значения

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

Когда по итогам обхода нужны суммы или обработка в разрезе групп (например, по каждому контрагенту), есть два подхода. Первый — перебор с накоплением в соответствии:

ИтогиПоКонтрагентам = Новый Соответствие;

Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл

ТекущаяСумма = ИтогиПоКонтрагентам[СтрокаТЗ.Контрагент];

Если ТекущаяСумма = Неопределено Тогда

ТекущаяСумма = 0;

КонецЕсли;

ИтогиПоКонтрагентам.Вставить(СтрокаТЗ.Контрагент,

ТекущаяСумма + СтрокаТЗ.Сумма);

КонецЦикла;

Второй подход — свернуть таблицу заранее методом Свернуть("Контрагент", "Сумма") и обходить уже сгруппированные данные. Это короче, но меняет исходную таблицу, поэтому при необходимости сохранить детальные строки работайте с копией: ТЗИтоги = ТаблицаЗначений.Скопировать(); ТЗИтоги.Свернуть(...).

Для сложной группировки с несколькими итоговыми полями и фильтрами эффективнее передать таблицу в запрос через временную таблицу (Запрос.УстановитьПараметр("ТЗ", ТаблицаЗначений) и конструкция ПОМЕСТИТЬ) и обходить уже выборку запроса — с группировками, итогами по иерархии и упорядочиванием.

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

Производительность: как не замедлить обработку

Сам обход таблицы значений — операция быстрая даже на сотнях тысяч строк. Медленным код становится из-за того, что делается внутри цикла. Главные источники проблем:

  • запросы к базе данных внутри цикла — каждый запрос это обращение к СУБД, и тысяча строк превращается в тысячу обращений;
  • повторный поиск методом Найти по неиндексированной таблице внутри цикла — получается квадратичная сложность;
  • конкатенация длинных строк через + в большом цикле — для накопления текста лучше использовать массив и СтрСоединить;
  • обращение к реквизитам через точку у ссылочных значений (СтрокаТЗ.Номенклатура.Наименование) — каждое такое обращение может тянуть данные объекта из базы.

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

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

Типичные ошибки
01
Удаление строк в цикле «Для Каждого» — пропуск строк или ошибка; удаляйте по индексу с конца или через массив строк
02
Запрос внутри цикла по каждой строке — обработка на тысячах строк выполняется минутами вместо секунд
03
Использование Количество() без «-1» в цикле по индексу — обращение за границу таблицы и ошибка
04
Поиск Найти() по неиндексированной таблице внутри цикла — квадратичный рост времени выполнения
05
Изменение исходной таблицы методом Свернуть без копии — теряются детальные строки, нужные дальше по коду

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

После написания обхода убедитесь, что обработано ожидаемое количество строк и данные не искажены. Простейший способ — счётчики: заведите переменные «обработано», «пропущено», «ошибок» и выведите их после цикла. Расхождение суммы счётчиков с ТаблицаЗначений.Количество() сразу покажет потерянные строки.

Для обходов, создающих документы или записывающих данные, выполняйте первый прогон в тестовой базе или в режиме без записи (флаг «только проверка»), и сверяйте итоговые суммы по обработанным строкам с исходной таблицей через ТаблицаЗначений.Итог("Сумма").

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

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

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

Совпадает с Количество() таблицы или с размером выборки НайтиСтроки

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

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

Итоговые суммы после обхода
Норма

Итог() по числовой колонке совпадает с контрольной суммой до обработки

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

Сумма изменилась без причин — где-то в цикле лишнее присваивание или двойная обработка

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

Обработка тысяч строк занимает секунды

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

Время растёт непропорционально объёму — внутри цикла запрос или поиск без индекса

Частые вопросы по обходу таблиц значений

Итог

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

Для обычного обхода используйте «Для Каждого», для работы с индексами и удаления — цикл со счётчиком с конца таблицы, для отбора строк — НайтиСтроки с индексом. Главные источники проблем: запросы внутри цикла, удаление строк при прямом обходе и поиск без индекса на больших таблицах.

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

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

Можно ли обойти таблицу значений в обратном порядке?

Да, но только циклом по индексу: идите от ТаблицаЗначений.Количество() - 1 до нуля с шагом -1 через конструкцию Для ... Цикл с вычислением индекса вручную или через Пока. Конструкция «Для Каждого» всегда идёт от первой строки к последней и обратный порядок не поддерживает. Альтернатива — предварительно отсортировать таблицу методом Сортировать с указанием «Убыв».

Как в цикле узнать номер текущей строки?

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

Что быстрее: обход таблицы значений или запрос?

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

Как обойти только уникальные значения колонки?

Скопируйте таблицу и сверните её по нужной колонке без суммируемых полей: ТЗУникальные = ТаблицаЗначений.Скопировать(); ТЗУникальные.Свернуть("Номенклатура") — затем обходите свёрнутую таблицу. Альтернатива без копирования: в цикле проверять соответствие уже обработанных значений и пропускать повторы через Продолжить.

Почему при удалении строк в цикле пропускаются строки?

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