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