При работе с языком запросов 1С программисты часто сталкиваются с необходимостью получения данных не только из «шапки» документа, но и из его табличных частей. Существует несколько способов реализации этой задачи, и выбор конкретного метода зависит от структуры вашего кода и требований к производительности. В этой статье мы подробно разберем, как правильно обходить строки табличных частей, почему возникают ошибки итератора и какие подходы считаются наиболее оптимальными.
Рассмотрим ситуацию, когда в тексте запроса используется конструкция выбора вложенной таблицы. Это именно тот случай, который вызвал затруднения у автора темы. В тексте запроса поле табличной части выбирается в скобках, что создает вложенную структуру в результате запроса.
Проанализируем пример кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказПоставщику.Номер КАК Номер,
| ЗаказПоставщику.Товары.(
| Номенклатура КАК Номенклатура,
| ДатаПоступления КАК ДатаПоступления
| ) КАК Товары
|ИЗ
| Документ.ЗаказПоставщику КАК ЗаказПоставщику";
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Здесь мы находимся на уровне документа (шапки)
// Чтобы перебрать товары, нужно создать вложенную выборку
ВыборкаТовары = ВыборкаДетальныеЗаписи.Товары.Выбрать();
Пока ВыборкаТовары.Следующий() Цикл
// Работаем с конкретной строкой табличной части
Сообщить("Номенклатура: " + ВыборкаТовары.Номенклатура);
КонецЦикла;
КонецЦикла;
Важный нюанс: Ошибка «Итератор для значения не определен» возникает, если попытаться перебрать поле ВыборкаДетальныеЗаписи.Товары с помощью цикла Для каждого. В режиме выборки (метод Выбрать()) вложенная таблица является объектом типа РезультатЗапросаВыборка, а не коллекцией или массивом. Поэтому для неё обязательно нужно снова вызывать метод Выбрать() и использовать цикл Пока ... Следующий().
Профессиональная разработка в 1С чаще предполагает обращение напрямую к физической таблице табличной части документа. Например, вместо таблицы Документ.ЗаказПоставщику мы обращаемся к Документ.ЗаказПоставщику.Товары. Это позволяет избежать вложенных конструкций в запросе, которые могут работать медленнее на больших объемах данных.
Выясним, как это реализовать на практике:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказТовары.Ссылка КАК ДокументСсылка,
| ЗаказТовары.Номенклатура КАК Номенклатура,
| ЗаказТовары.ДатаПоступления КАК ДатаПоступления
|ИЗ
| Документ.ЗаказПоставщику.Товары КАК ЗаказТовары
|ГДЕ
| ЗаказТовары.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", НужныйДокументСсылка);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// Здесь каждая итерация — это отдельная строка ТЧ
Сообщить(Выборка.Номенклатура);
КонецЦикла;
В этом случае поле Ссылка автоматически содержит ссылку на родительский документ. Это решает проблему идентификации того, к какому документу относится товар, если запрос выполняется по нескольким документам сразу.
Для построения иерархических отчетов или обработки данных «Документ -> Его строки» наиболее производительным считается использование ключевого слова ИТОГИ. Это позволяет платформе оптимизировать SQL-запрос.
Разберем пример построения обхода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказТовары.Ссылка КАК Ссылка,
| ЗаказТовары.Номенклатура КАК Номенклатура
|ИЗ
| Документ.ЗаказПоставщику.Товары КАК ЗаказТовары
|ИТОГИ ПО
| Ссылка";
РезультатЗапроса = Запрос.Выполнить();
// Выбираем по группировкам (уровень ссылок на документы)
ВыборкаДокументы = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаДокументы.Следующий() Цикл
Сообщить("Обработка документа: " + ВыборкаДокументы.Ссылка);
// Выбираем детальные записи только для текущего документа
ВыборкаСтроки = ВыборкаДокументы.Выбрать();
Пока ВыборкаСтроки.Следующий() Цикл
Сообщить(" - Товар: " + ВыборкаСтроки.Номенклатура);
КонецЦикла;
КонецЦикла;
Проанализируем ситуацию, когда данные выгружаются методом Выгрузить(). Здесь поведение объектов меняется:
РезультатЗапроса.Выгрузить(), то в колонке «Товары» (для первого способа) будет лежать полноценный объект ТаблицаЗначений.Для каждого СтрокаТЧ Из СтрокаОсновная.Товары Цикл будет работать, так как ТаблицаЗначений является итерируемой коллекцией.Посмотрим на ситуацию со стороны производительности. Использование конструкции Документ.Товары.(...) создает специфическую нагрузку. Если вам нужно просто перебрать товары одного конкретного документа, который уже открыт в форме (объект Объект), лучше вообще не использовать запрос к базе данных, а обращаться к коллекции напрямую:
Для Каждого СтрокаТовара Из Объект.Товары Цикл
// Работаем с данными формы или объекта в памяти
Сообщить(СтрокаТовара.Номенклатура);
КонецЦикла;
Важный момент: Помните, что запрос обращается к данным, записанным в базе. Если пользователь изменил состав строк в табличной части, но еще не нажал кнопку «Записать», запрос эти изменения не увидит. Для обработки «летучих» данных используйте только перебор коллекции Объект.Товары или метод Выгрузить() табличной части.
Подводя итог, выделим основные правила:
Выбрать(), делайте вложенный цикл с Выбрать() для поля табличной части.Документ.Имя.Товары с использованием ИТОГИ ПО Ссылка.Следующий(), если таблица значений — Для каждого.