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

· 9 мин

Содержание

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

Все примеры — для платформы 8.3, код выполняется на сервере: на тонком клиенте объект ТаблицаЗначений недоступен.

1. Цикл Для Каждого — базовый обход таблицы значений

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

Для Каждого СтрокаТЗ Из ТаблицаТоваров Цикл
    СтрокаТЗ.Сумма = СтрокаТЗ.Количество * СтрокаТЗ.Цена;
КонецЦикла;

Переменная СтрокаТЗ — это ссылка на настоящую строку таблицы, а не копия. Поэтому присваивание СтрокаТЗ.Сумма = … сразу меняет данные в самой таблице.

Когда Для Каждого подходит

  • ✅ читаете или меняете значения в существующих строках;
  • ✅ порядок обхода — сверху вниз, и он вас устраивает;
  • ✅ количество строк в процессе не меняется.

Когда не подходит

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

Обход колонок внутри строки

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

Для Каждого СтрокаТЗ Из ТЗ Цикл
    Для Каждого Колонка Из ТЗ.Колонки Цикл
        Значение = СтрокаТЗ[Колонка.Имя];
        Сообщить(Колонка.Имя + ": " + Строка(Значение));
    КонецЦикла;
КонецЦикла;

Обращение через квадратные скобки СтрокаТЗ["Сумма"] работает так же, как СтрокаТЗ.Сумма, но позволяет подставить имя колонки из переменной.

2. Цикл по индексу строк таблицы: Для и Пока

Строки таблицы значений нумеруются с нуля. Последняя строка имеет индекс ТЗ.Количество() - 1. К строке обращаются через ТЗ[Индекс] или ТЗ.Получить(Индекс) — это одно и то же.

Прямой обход по индексу

Для Индекс = 0 По ТЗ.Количество() - 1 Цикл
    СтрокаТЗ = ТЗ[Индекс];
    СтрокаТЗ.НомерСтроки = Индекс + 1;
КонецЦикла;

Удобно, когда нужен номер строки или соседняя строка: ТЗ[Индекс - 1] для сравнения с предыдущей. Не забудьте проверить, что Индекс > 0, иначе получите ошибку выхода за границу.

Если таблица пустая, ТЗ.Количество() - 1 равно -1, и цикл Для 0 По -1 не выполнится ни разу. Отдельная проверка на пустоту не нужна.

Обратный обход — для удаления строк

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

ВсегоСтрок = ТЗ.Количество();
Для Сч = 1 По ВсегоСтрок Цикл
    Индекс = ВсегоСтрок - Сч;
    Если ТЗ[Индекс].Количество = 0 Тогда
        ТЗ.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

Проверим на таблице из 5 строк: индексы идут 4, 3, 2, 1, 0. Если удалить строку 4, строки 0–3 остаются на своих местах, и следующая итерация корректно берёт индекс 3. Количество строк фиксируется в переменной ВсегоСтрок один раз — так индекс не «плывёт» после удалений.

Цикл Пока

Пока удобен, когда шаг зависит от условия: например, при удалении строк в прямом порядке.

Индекс = 0;
Пока Индекс < ТЗ.Количество() Цикл
    Если ТЗ[Индекс].Количество = 0 Тогда
        ТЗ.Удалить(Индекс); // индекс не увеличиваем: на это место встала следующая строка
    Иначе
        Индекс = Индекс + 1;
    КонецЕсли;
КонецЦикла;

Условие Индекс < ТЗ.Количество() пересчитывается на каждой итерации, поэтому цикл корректно учитывает уменьшение таблицы.

Самая частая причина зависания — забытое Индекс = Индекс + 1 в ветке, где строка не удаляется. Цикл Пока без изменения счётчика никогда не завершится.

3. Поиск строк вместо полного перебора таблицы

Перебор целиком нужен, когда обрабатываете все строки. Если ищете конкретные строки — используйте встроенный поиск. Отдельного метода НайтиСтроку() у таблицы значений нет; есть два метода поиска.

МетодЧто возвращаетКогда использовать
Найти(Значение, "Колонки")первую найденную строку или Неопределенонужна одна строка, поиск по одному значению
НайтиСтроки(СтруктураОтбора)массив строк (может быть пустым)отбор по нескольким колонкам, нужны все совпадения

Найти

СтрокаТЗ = ТЗ.Найти(Номенклатура, "Номенклатура");
Если СтрокаТЗ = Неопределено Тогда
    СтрокаТЗ = ТЗ.Добавить();
    СтрокаТЗ.Номенклатура = Номенклатура;
КонецЕсли;
СтрокаТЗ.Количество = СтрокаТЗ.Количество + 1;

Второй параметр можно не указывать, тогда поиск идёт по всем колонкам. Указывайте его всегда: так быстрее и нет ложных совпадений в другой колонке.

НайтиСтроки и индексы

ТЗ.Индексы.Добавить("Номенклатура, Склад");

Отбор = Новый Структура("Номенклатура, Склад", Номенклатура, Склад);
НайденныеСтроки = ТЗ.НайтиСтроки(Отбор);
Для Каждого СтрокаТЗ Из НайденныеСтроки Цикл
    СтрокаТЗ.Обработано = Истина;
КонецЦикла;

Индекс добавьте один раз, до цикла поиска. Без индекса каждый вызов НайтиСтроки просматривает всю таблицу. С индексом по тем же колонкам, что в отборе, поиск идёт по индексу и ускоряется на порядки на больших таблицах.

В массиве НайденныеСтроки лежат ссылки на строки исходной таблицы. Меняете их — меняется таблица.

Соответствие вместо вложенного цикла

Типичная медленная конструкция — цикл по таблице A, внутри полный цикл по таблице B. При 10 000 строк в каждой это 100 миллионов сравнений.

Решение — один раз разложить таблицу B в Соответствие, а потом искать по ключу:

ЦеныПоТовару = Новый Соответствие;
Для Каждого СтрокаЦены Из ТаблицаЦен Цикл
    ЦеныПоТовару.Вставить(СтрокаЦены.Номенклатура, СтрокаЦены.Цена);
КонецЦикла;

Для Каждого СтрокаТЗ Из ТаблицаТоваров Цикл
    Цена = ЦеныПоТовару.Получить(СтрокаТЗ.Номенклатура);
    СтрокаТЗ.Цена = ?(Цена = Неопределено, 0, Цена);
КонецЦикла;

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

4. Обработка колонки целиком без цикла

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

ЗадачаМетодПример
получить все значения колонкиВыгрузитьКолонкуМассив = ТЗ.ВыгрузитьКолонку("Номенклатура");
записать массив в колонкуЗагрузитьКолонкуТЗ.ЗагрузитьКолонку(Массив, "Цена");
заполнить колонку одним значениемЗаполнитьЗначенияТЗ.ЗаполнитьЗначения(Ложь, "Обработано");
сумма по колонкеИтогВсего = ТЗ.Итог("Сумма");
сгруппировать и просуммироватьСвернутьТЗ.Свернуть("Номенклатура", "Количество, Сумма");
копия с отборомСкопироватьКопия = ТЗ.Скопировать(Отбор);

Типовой сценарий: выгрузить, обработать, загрузить

Товары = ТЗ.ВыгрузитьКолонку("Номенклатура");
Цены = ПолучитьЦены(Товары); // ваша функция: один запрос на все товары, возвращает массив
ТЗ.ЗагрузитьКолонку(Цены, "Цена");

Смысл в том, чтобы вызвать запрос к базе один раз на весь массив, а не по строке. ЗагрузитьКолонку записывает элементы массива в строки по порядку, поэтому массив должен иметь ту же длину и тот же порядок, что и таблица.

Убрать дубли

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

Уникальные = ТЗ.Скопировать(, "Номенклатура");
Уникальные.Свернуть("Номенклатура");
СписокТоваров = Уникальные.ВыгрузитьКолонку("Номенклатура");

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

5. Обход таблицы значений через запрос

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

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.УстановитьПараметр("Таблица", ТЗ);
Запрос.Текст =
"ВЫБРАТЬ
|    Т.Номенклатура КАК Номенклатура,
|    Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
|    &Таблица КАК Т
|;
|
|ВЫБРАТЬ
|    ВТ.Номенклатура КАК Номенклатура,
|    СУММА(ВТ.Количество) КАК Количество
|ИЗ
|    ВТ_Товары КАК ВТ
|СГРУППИРОВАТЬ ПО
|    ВТ.Номенклатура";

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    // Выборка.Номенклатура, Выборка.Количество
КонецЦикла;

Три правила, без которых запрос упадёт:

  1. Таблицу-параметр можно использовать только в запросе с ПОМЕСТИТЬ — сначала во временную таблицу, потом работа с ней.
  2. Колонки таблицы значений должны иметь тип. Создавайте их с описанием типов: ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число")). Колонка без типа вызывает ошибку при выполнении запроса.
  3. В запросе нельзя выбрать колонку, которой нет в таблице: имена должны совпадать дословно.

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

6. Тяжёлая обработка: пакеты и фоновые задания

Сам цикл по таблице на 100 000 строк выполняется быстро. Медленно то, что делается внутри: запись объектов, обращение к базе, HTTP-запросы.

Отложенная обработка

Не пишите в базу на каждой итерации. Соберите изменения в цикле, а запишите одной порцией после него:

  • 📦 сначала обход таблицы и подготовка данных в памяти;
  • 📦 потом один запрос на получение недостающих данных;
  • 📦 запись объектов — отдельным проходом, в транзакции по порциям (например, по 500 строк), чтобы при ошибке не откатывать всё.

Параллельная обработка в фоновых заданиях

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

РазмерЧасти = 5000;
ВсегоСтрок = ТЗ.Количество();
Начало = 0;
Пока Начало < ВсегоСтрок Цикл
    Часть = ТЗ.СкопироватьКолонки();
    Конец = Мин(Начало + РазмерЧасти, ВсегоСтрок) - 1;
    Для Индекс = Начало По Конец Цикл
        ЗаполнитьЗначенияСвойств(Часть.Добавить(), ТЗ[Индекс]);
    КонецЦикла;
    Параметры = Новый Массив;
    Параметры.Добавить(Часть);
    ФоновыеЗадания.Выполнить("МойОбщийМодуль.ОбработатьЧасть", Параметры);
    Начало = Конец + 1;
КонецЦикла;

Процедура ОбработатьЧасть должна быть экспортной и лежать в серверном общем модуле. Задание получает копию данных: изменения внутри него не вернутся в исходную таблицу. Результат передавайте через временное хранилище или записывайте в базу.

Каждое фоновое задание занимает ресурсы рабочего процесса. Не запускайте десятки заданий на рабочей базе в разгар дня: пользователи почувствуют замедление. Начните с 2–4 частей и замерьте.

7. Ошибки при обходе таблицы значений в цикле

Диагностика: выберите симптом

Ещё одна неочевидная ловушка — сравнение строк таблицы. СтрокаТЗ1 = СтрокаТЗ2 проверяет, что это одна и та же строка, а не что у строк одинаковые значения. Для сравнения содержимого сравнивайте колонки.

Обращение через точку к реквизитам ссылки внутри цикла (СтрокаТЗ.Номенклатура.Артикул) на каждой итерации читает объект из базы. На больших таблицах получите сотни тысяч лишних обращений. Получите нужные реквизиты заранее одним запросом.

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

Можно ли обойти таблицу значений на клиенте?
Нет, объект ТаблицаЗначений доступен только на сервере. На форме таблица хранится как коллекция данных формы: её можно перебрать Для Каждого на клиенте, а для серверной обработки преобразуйте её функцией РеквизитФормыВЗначение.
Как выйти из цикла досрочно?
Оператором Прервать — цикл завершится сразу. Оператор Продолжить пропускает остаток текущей итерации и переходит к следующей строке.
Что быстрее: Для Каждого или цикл по индексу?
На практике разница незаметна. Скорость определяет то, что происходит внутри цикла: запросы, поиск, обращения через точку.
Как обойти таблицу в обратном порядке без удаления строк?
Сохраните количество строк в переменную и берите строку по индексу ВсегоСтрок - Сч, где Сч растёт от 1. Другой вариант — отсортировать копию таблицы методом Сортировать с направлением Убыв.
Можно ли добавлять строки во время обхода?
В цикл Для Каждого по той же таблице — не стоит, поведение будет непредсказуемым. Собирайте новые строки во вторую таблицу и после цикла перенесите их.