Перебор строк таблицы значений во встроенном языке 1С: циклы, поиск, запросы
Содержание
Обойти таблицу значений в цикле 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. Обход таблицы значений через запрос
Когда к таблице нужно применить соединения, группировки с условиями или связать её с данными базы — передайте её в запрос. Это удобнее и обычно быстрее, чем вложенные циклы.
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.УстановитьПараметр("Таблица", ТЗ);
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура,
| Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &Таблица КАК Т
|;
|
|ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| СУММА(ВТ.Количество) КАК Количество
|ИЗ
| ВТ_Товары КАК ВТ
|СГРУППИРОВАТЬ ПО
| ВТ.Номенклатура";
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Выборка.Номенклатура, Выборка.Количество
КонецЦикла;
Три правила, без которых запрос упадёт:
- Таблицу-параметр можно использовать только в запросе с
ПОМЕСТИТЬ— сначала во временную таблицу, потом работа с ней. - Колонки таблицы значений должны иметь тип. Создавайте их с описанием типов:
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число")). Колонка без типа вызывает ошибку при выполнении запроса. - В запросе нельзя выбрать колонку, которой нет в таблице: имена должны совпадать дословно.
Обход результата выборкой (Пока Выборка.Следующий()) экономит память: строки читаются по одной. Если результат снова нужен таблицей, вызовите Запрос.Выполнить().Выгрузить().
6. Тяжёлая обработка: пакеты и фоновые задания
Сам цикл по таблице на 100 000 строк выполняется быстро. Медленно то, что делается внутри: запись объектов, обращение к базе, HTTP-запросы.
Отложенная обработка
Не пишите в базу на каждой итерации. Соберите изменения в цикле, а запишите одной порцией после него:
- 📦 сначала обход таблицы и подготовка данных в памяти;
- 📦 потом один запрос на получение недостающих данных;
- 📦 запись объектов — отдельным проходом, в транзакции по порциям (например, по 500 строк), чтобы при ошибке не откатывать всё.
Параллельная обработка в фоновых заданиях
Если на каждую строку приходится долгая операция, таблицу можно разбить на части и отдать их фоновым заданиям:
РазмерЧасти = 5000;
ВсегоСтрок = ТЗ.Количество();
Начало = 0;
Пока Начало < ВсегоСтрок Цикл
Часть = ТЗ.СкопироватьКолонки();
Конец = Мин(Начало + РазмерЧасти, ВсегоСтрок) - 1;
Для Индекс = Начало По Конец Цикл
ЗаполнитьЗначенияСвойств(Часть.Добавить(), ТЗ[Индекс]);
КонецЦикла;
Параметры = Новый Массив;
Параметры.Добавить(Часть);
ФоновыеЗадания.Выполнить("МойОбщийМодуль.ОбработатьЧасть", Параметры);
Начало = Конец + 1;
КонецЦикла;
Процедура ОбработатьЧасть должна быть экспортной и лежать в серверном общем модуле. Задание получает копию данных: изменения внутри него не вернутся в исходную таблицу. Результат передавайте через временное хранилище или записывайте в базу.
Каждое фоновое задание занимает ресурсы рабочего процесса. Не запускайте десятки заданий на рабочей базе в разгар дня: пользователи почувствуют замедление. Начните с 2–4 частей и замерьте.
7. Ошибки при обходе таблицы значений в цикле
Ещё одна неочевидная ловушка — сравнение строк таблицы. СтрокаТЗ1 = СтрокаТЗ2 проверяет, что это одна и та же строка, а не что у строк одинаковые значения. Для сравнения содержимого сравнивайте колонки.
Обращение через точку к реквизитам ссылки внутри цикла (СтрокаТЗ.Номенклатура.Артикул) на каждой итерации читает объект из базы. На больших таблицах получите сотни тысяч лишних обращений. Получите нужные реквизиты заранее одним запросом.