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