Обход строк таблицы значений во встроенном языке 1С: Для Каждого, индекс, удаление и скорость
Содержание
Перебрать таблицу значений в 1С 8.3 проще всего циклом Для Каждого Строка Из ТаблицаЗначений Цикл … КонецЦикла. Если нужен номер строки или строки удаляются по ходу, берите цикл по индексу: Для Индекс = 0 По Таблица.Количество() - 1 Цикл, а строку получайте через Таблица.Получить(Индекс) или Таблица[Индекс].
Типичная ситуация: запрос вернул 3 000 строк остатков, и в каждой нужно пересчитать сумму, часть строк выбросить, а по остальным собрать итог. Под каждую из этих трёх задач подходит свой цикл, а ошибка в выборе тихо портит данные: строки пропускаются без всякого сообщения.
Как устроена таблица значений и что даёт перебор
Таблица значений (ТаблицаЗначений) — коллекция строк в памяти. У неё есть коллекция Колонки и сами строки типа СтрокаТаблицыЗначений. Нумерация строк начинается с нуля: первая строка — Таблица[0], последняя — Таблица[Таблица.Количество() - 1].
Обратиться к значению в строке можно двумя способами:
- 🔹 через точку:
Строка.Количество, если имя колонки известно при написании кода; - 🔹 через квадратные скобки:
Строка["Количество"], если имя колонки хранится в переменной.
Таблица значений живёт только на сервере и в толстом клиенте. В управляемой форме реквизит с типом таблицы значений на клиенте виден как ДанныеФормыКоллекция. Перебирать его на клиенте можно тем же Для Каждого, но методы самой таблицы (Свернуть, ВыгрузитьКолонку, Скопировать) там недоступны. Для них реквизит переводят в значение на сервере функцией РеквизитФормыВЗначение.
Для перебора в языке есть три вида циклов. Каждый подходит под свою задачу:
| Цикл | Когда брать | Что учесть |
|---|---|---|
Для Каждого … Из … Цикл | читаем или меняем значения в строках | нельзя удалять строки внутри цикла |
Для Индекс = … По … Цикл | нужен номер строки, обратный обход, удаление | индекс с нуля, граница Количество() - 1 |
Пока … Цикл | число итераций меняется по ходу | счётчик ведём сами, легко зациклить |
Управлять ходом цикла помогают два оператора: Продолжить переходит к следующей итерации, Прервать выходит из цикла совсем.
Перебор по индексу: метод Получить() и квадратные скобки
Классический вариант — счётчик от нуля до последнего индекса. Строку берут методом Получить() или обращением по индексу, разницы в результате нет.
Для Индекс = 0 По ТаблицаТоваров.Количество() - 1 Цикл
Строка = ТаблицаТоваров.Получить(Индекс);
// то же самое: Строка = ТаблицаТоваров[Индекс];
Строка.Сумма = Строка.Количество * Строка.Цена;
КонецЦикла;
Граница Количество() - 1 вычисляется один раз, при входе в цикл. Если таблица пустая, граница равна −1, и цикл не выполнится ни разу. Ошибки не будет.
Цикл по индексу нужен, когда:
- 📌 номер строки участвует в логике (каждая пятая строка, сравнение с соседней строкой);
- 📌 нужно идти с конца — например, при удалении строк;
- 📌 обработку надо начать не с первой строки.
Пример со сравнением соседних строк — отметить строку, если цена выросла относительно предыдущей:
Для Индекс = 1 По ТаблицаЦен.Количество() - 1 Цикл
Текущая = ТаблицаЦен[Индекс];
Предыдущая = ТаблицаЦен[Индекс - 1];
Текущая.Рост = (Текущая.Цена > Предыдущая.Цена);
КонецЦикла;
Если нужно узнать номер строки, которую вы уже держите в руках, используйте Таблица.Индекс(Строка). Метод вернёт число от нуля или −1, если строки в таблице нет.
Номер строки для пользователя (в сообщении, в печатной форме) выводите как Индекс + 1. Склеивать число со строкой лучше через Формат(Индекс + 1, "ЧГ=0"), иначе на номере 1000 появится пробел-разделитель.
Цикл Для Каждого: короткий и читаемый обход строк
Для чтения и правки значений хватает цикла Для Каждого. Он не требует счётчика, и в нём не ошибёшься с границей.
ИтогоСумма = 0;
Для Каждого Строка Из ТаблицаТоваров Цикл
Если Строка.Количество = 0 Тогда
Продолжить;
КонецЕсли;
Строка.Сумма = Строка.Количество * Строка.Цена;
ИтогоСумма = ИтогоСумма + Строка.Сумма;
КонецЦикла;
Переменная Строка — это ссылка на настоящую строку таблицы, а не её копия. Всё, что вы присвоите полям Строка, сразу окажется в таблице. Возвращать строку обратно не нужно.
Что можно делать внутри Для Каждого:
- ✅ читать и менять значения в колонках;
- ✅ выходить досрочно через
Прервать, когда нужная строка найдена; - ✅ пропускать строки через
Продолжить.
Чего делать нельзя:
- ❌ удалять строки перебираемой таблицы — цикл перепрыгнет через часть строк;
- ❌ добавлять строки в ту же таблицу — результат зависит от позиции вставки, такой код трудно отладить.
Обход колонок внутри строки
Когда структура таблицы заранее неизвестна (например, её вернула универсальная функция), перебирают и строки, и колонки:
Для Каждого Строка Из Таблица Цикл
Для Каждого Колонка Из Таблица.Колонки Цикл
Значение = Строка[Колонка.Имя];
Сообщить(Колонка.Имя + ": " + Строка(Значение));
КонецЦикла;
КонецЦикла;
Так удобно выгружать таблицу в текст, CSV или проверять, в каких колонках пустые значения.
Как удалять и пропускать строки при переборе
Здесь чаще всего и возникают ошибки. Есть два разных намерения, и решаются они по-разному.
Пропустить строку (не обрабатывать, но оставить в таблице) — это Продолжить в любом цикле.
Удалить строку из таблицы — только обходом с конца. Если идти с начала, после удаления строки 5 на её место встаёт строка 6, а счётчик уже переходит к 6, и бывшая шестая строка остаётся непроверенной.
Правильный обратный цикл:
ВсегоСтрок = ТаблицаТоваров.Количество();
Для Сч = 1 По ВсегоСтрок Цикл
Индекс = ВсегоСтрок - Сч;
Строка = ТаблицаТоваров[Индекс];
Если Строка.Количество = 0 Тогда
ТаблицаТоваров.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Проверим на таблице из 4 строк: Индекс пройдёт значения 3, 2, 1, 0. Удаление строки 2 сдвигает только строку 3, а её мы уже проверили. Ничего не теряется.
Метод Удалить() принимает и индекс, и саму строку: ТаблицаТоваров.Удалить(Строка).
Второй надёжный способ — сначала собрать строки на удаление в массив, потом удалить их отдельным циклом:
КУдалению = Новый Массив;
Для Каждого Строка Из ТаблицаТоваров Цикл
Если Строка.Количество = 0 Тогда
КУдалению.Добавить(Строка);
КонецЕсли;
КонецЦикла;
Для Каждого Строка Из КУдалению Цикл
ТаблицаТоваров.Удалить(Строка);
КонецЦикла;
Если условие отбора простое (равенство значений колонок), массив даст метод НайтиСтроки():
Отбор = Новый Структура("Количество", 0);
Для Каждого Строка Из ТаблицаТоваров.НайтиСтроки(Отбор) Цикл
ТаблицаТоваров.Удалить(Строка);
КонецЦикла;
Здесь цикл идёт по массиву найденных строк, а не по самой таблице, поэтому удаление ничего не ломает.
Если вы не удаляете строки, а хотите получить из таблицы только нужные, удобнее сделать новую таблицу: Отобранные = ТаблицаТоваров.Скопировать(ТаблицаТоваров.НайтиСтроки(Отбор)). Исходная таблица останется целой.
Ошибка внутри итерации: Попытка … Исключение
Иногда одна «плохая» строка не должна останавливать всю обработку: например, при загрузке данных из файла в строке оказалась дата текстом. Тогда тело цикла оборачивают в Попытка:
Для Каждого Строка Из ТаблицаЗагрузки Цикл
Попытка
Строка.Дата = Дата(Строка.ДатаСтрокой);
Исключение
Строка.ТекстОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
Продолжить;
КонецПопытки;
// дальнейшая обработка корректной строки
КонецЦикла;
Не ставьте Попытка вокруг всего цикла, если хотите продолжать после ошибки: первая же ошибка выкинет вас из цикла целиком. И не используйте исключения вместо проверок — Если ЗначениеЗаполнено(…) дешевле и понятнее.
Какой способ перебора быстрее
На простом переборе разница между Для Каждого и циклом по индексу невелика. Таблицу на десятки тысяч строк оба варианта проходят быстро, а время уходит на то, что вы делаете внутри цикла.
Что действительно тормозит:
| Что внутри цикла | Почему медленно | Чем заменить |
|---|---|---|
| запрос к базе на каждой строке | тысячи обращений к СУБД | один запрос с временной таблицей до цикла |
Найти() или НайтиСтроки() по второй таблице | полный просмотр второй таблицы на каждой итерации | индекс по колонке или Соответствие |
обращение через точку к реквизитам ссылки (Строка.Номенклатура.Артикул) | скрытый запрос к базе | получить реквизит в запросе отдельной колонкой |
Сообщить() на каждой строке | вывод тысяч сообщений | собрать текст и вывести один раз |
Проверить свой случай можно замером:
Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
Для Каждого Строка Из Таблица Цикл
// обработка
КонецЦикла;
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить("Обход занял, мс: " + Формат(Длительность, "ЧГ=0; ЧН=0"));
Для честного сравнения запускайте замер несколько раз на одной таблице и смотрите среднее.
Подробную картину по строкам кода даёт замер производительности в отладчике Конфигуратора или EDT. Он показывает, какая строка цикла съела больше всего времени.
Оптимизация: индексы, соответствия и замена циклов методами таблицы
Индекс по колонке для поиска
Если внутри цикла вы ищете строки в другой таблице по одной и той же колонке, создайте индекс:
Остатки.Индексы.Добавить("Номенклатура");
Для Каждого Строка Из ТаблицаТоваров Цикл
Отбор = Новый Структура("Номенклатура", Строка.Номенклатура);
Найденные = Остатки.НайтиСтроки(Отбор);
Если Найденные.Количество() > 0 Тогда
Строка.Остаток = Найденные[0].Количество;
КонецЕсли;
КонецЦикла;
С индексом НайтиСтроки() не просматривает всю таблицу, а сразу находит строки по ключу. На таблицах в тысячи строк разница заметна сразу. Создавайте индекс один раз до цикла, а не внутри.
Соответствие вместо поиска
Когда ключ уникален, ещё проще заранее разложить таблицу в Соответствие:
ОстаткиПоТовару = Новый Соответствие;
Для Каждого Строка Из Остатки Цикл
ОстаткиПоТовару.Вставить(Строка.Номенклатура, Строка.Количество);
КонецЦикла;
Для Каждого Строка Из ТаблицаТоваров Цикл
Остаток = ОстаткиПоТовару.Получить(Строка.Номенклатура);
Строка.Остаток = ?(Остаток = Неопределено, 0, Остаток);
КонецЦикла;
Два линейных прохода вместо поиска на каждой итерации.
Методы таблицы, которые заменяют цикл
Часть задач вообще не требует ручного перебора. Платформа делает это сама и быстрее:
- 📊
Таблица.Итог("Сумма")— сумма по колонке; - 📊
Таблица.Свернуть("Номенклатура", "Количество, Сумма")— группировка с суммированием; - 📊
Таблица.ВыгрузитьКолонку("Номенклатура")— массив значений колонки; - 📊
Таблица.ЗаполнитьЗначения(0, "Скидка")— одно значение во все строки колонки; - 📊
Таблица.Сортировать("Дата Убыв")— сортировка без самописного алгоритма; - 📊
Таблица.Скопировать(, "Номенклатура, Количество")— новая таблица только с нужными колонками.
Если таблица пришла из запроса, многие расчёты выгоднее перенести в сам запрос: группировку, отбор, соединение с остатками. Тогда цикл остаётся только для того, что запросом не сделать.
Где чаще всего ошибаются при обходе таблицы
- ⚠️ Граница
По Таблица.Количество()без минус единицы. На последней итерации индекс выйдет за пределы, и цикл упадёт с ошибкойИндекс находится за границами диапазона. - ⚠️ Счёт от единицы. Код
Для Индекс = 1 По Таблица.Количество() Цикл Строка = Таблица[Индекс]пропустит первую строку и упадёт на последней. - ⚠️ Опечатка в имени колонки через скобки.
Строка["Колличество"]не проверяется при сохранении модуля, ошибкаПоле объекта не обнаруженовсплывёт только на выполнении. - ⚠️ Перебор табличной части формы на клиенте с вызовом сервера на каждой строке. Каждый вызов — отдельный обмен с сервером. Соберите данные и сделайте один серверный вызов.
- ⚠️ Изменение строки-копии. Если вы взяли строки через
Скопировать(), правки в новой таблице не попадут в исходную. Это разные объекты.
Частые вопросы
Чем отличается Получить(Индекс) от Таблица[Индекс]?
Получить() нагляднее в чужом коде.Как перебрать табличную часть документа?
Для Каждого Строка Из Документ.Товары Цикл. Удалять строки табличной части тоже нужно обратным циклом или через массив.Можно ли прервать вложенный цикл и выйти сразу из обоих?
Прервать выходит только из текущего цикла. Для выхода из внешнего заведите флаг, проверяйте его после внутреннего цикла и вызывайте Прервать ещё раз.Почему на клиенте нет метода Свернуть() у таблицы формы?
ДанныеФормыКоллекция, а не таблица значений. Переведите его на сервере через РеквизитФормыВЗначение, обработайте и верните через ЗначениеВРеквизитФормы.Как перебрать таблицу значений в обратном порядке без удаления?
Количество - Сч. Либо отсортируйте копию таблицы по нужной колонке с суффиксом Убыв.