Почему при выборке строк документа из запроса в 1С 7.7 не считываются реквизиты табличной части и как это исправить?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При разработке отчетов и обработок в платформе 1С:Предприятие 7.7 часто возникает задача: выбрать документы с помощью запроса, а затем перебрать строки их табличной части для получения номенклатуры, количества или сумм. Нередко разработчики сталкиваются с ситуацией, когда цикл по строкам выполняется, но значения реквизитов (например, наименование или код номенклатуры) оказываются пустыми, либо вызов метода ПолучитьСтроку() приводит к бесконечному циклу. Разберем механизм возникновения этой ошибки и рассмотрим корректные способы ее решения.

Анализ проблемы: почему обращение через цепочку точек дает сбой

Рассмотрим фрагмент исходного кода, в котором возникает ошибка:


Пока Запрос.Группировка = 1 Цикл
    Строк = Запрос.Реализация.КоличествоСтрок();
    к = 1;
    Пока к <= Строк Цикл
        Запрос.Реализация.ПолучитьСтрокуПоНомеру(к);
        Сообщить(Строка(к) + " Имя: " + Запрос.Реализация.Номенклатура.Наименование);
        к = к + 1;
    КонецЦикла;
КонецЦикла;

Выясним причину такого поведения платформы. В 1С 7.7 конструкция Запрос.Реализация при каждом отдельном обращении динамически создает временный контекстный объект документа. Это приводит к следующим системным эффектам:

  1. Когда мы выполняем Запрос.Реализация.ПолучитьСтрокуПоНомеру(к), платформа считывает нужную строку во временный объект, после чего этот объект сразу же удаляется из оперативной памяти.
  2. На следующем шаге при обращении к Запрос.Реализация.Номенклатура создается совершенно новый экземпляр объекта документа. В этом новом экземпляре номер текущей строки равен нулю, позиционирование сброшено, из-за чего реквизиты табличной части возвращают пустое значение.
  3. Если попытаться использовать классический цикл Пока Запрос.Реализация.ПолучитьСтроку() = 1 Цикл, на каждой итерации условия будет создаваться новый временный объект, считываться первая строка и возвращаться единица. В результате алгоритм входит в бесконечный цикл.

Способ 1. Разыменование объекта через промежуточную переменную

Самое простое и быстрое исправление заключается в сохранении ссылки на документ в отдельную объектную переменную перед началом обхода строк. В этом случае все вызовы методов позиционирования и чтения реквизитов выполняются в едином контексте конкретного экземпляра.

Разберем по шагам реализацию с методом ВыбратьСтроки():


Пока Запрос.Группировка(1) = 1 Цикл
    Док = Запрос.Реализация;
    Док.ВыбратьСтроки();
    Пока Док.ПолучитьСтроку() = 1 Цикл
        Сообщить("Номенклатура: " + Док.Номенклатура.Наименование + " | Код: " + Док.Номенклатура.Код);
    КонецЦикла;
КонецЦикла;

Также можно использовать явный перебор по номерам строк с помощью метода ПолучитьСтрокуПоНомеру(), сохранив объект в переменную:


Пока Запрос.Группировка(1) = 1 Цикл
    Док = Запрос.Реализация;
    ВсегоСтрок = Док.КоличествоСтрок();
    Для Сч = 1 По ВсегоСтрок Цикл
        Док.ПолучитьСтрокуПоНомеру(Сч);
        Сообщить("Строка " + Сч + ": " + Док.Номенклатура.Наименование);
    КонецЦикла;
КонецЦикла;

Способ 2. Получение строк табличной части средствами самого запроса

Обращение к объекту документа через переменную Док = Запрос.Реализация внутри цикла заставляет платформу при каждой итерации выполнять дополнительное считывание данных с диска или отправлять отдельный SQL-запрос к базе данных. Если документов много, это существенно снижает скорость работы.

Чтобы ускорить обработку, выберем строки непосредственно в тексте запроса. Чтобы одинаковые позиции номенклатуры в рамках одного документа не сворачивались в одну строку результата, добавим группировку по номеру строки документа:


Перем Запрос, ТекстЗапроса;
Запрос = СоздатьОбъект("Запрос");

ТекстЗапроса = "
|Период с ВыбНачПериода по ВыбКонПериода;
|ОбрабатыватьДокументы все;
|Реализация   = Документ.Реализация.ТекущийДокумент;
|СтрокаДок   = Документ.Реализация.НомерСтроки;
|Номенклатура = Документ.Реализация.Номенклатура;
|Группировка Реализация упорядочить по Реализация.ДатаДок;
|Группировка СтрокаДок;
|";

Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
    Пока Запрос.Группировка(1) = 1 Цикл
        // Обход документов
        Пока Запрос.Группировка(2) = 1 Цикл
            // Обход строк текущего документа
            Сообщить("Имя: " + Запрос.Номенклатура.Наименование + " | Код: " + Запрос.Номенклатура.Код);
        КонецЦикла;
    КонецЦикла;
КонецЕсли;

Этот подход работает значительно быстрее, так как платформа считывает весь необходимый объем данных пакетом при выполнении запроса, не обращаясь к базе на каждом шаге цикла.

Способ 3. Выгрузка табличной части в Таблицу Значений

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


ТЗ = СоздатьОбъект("ТаблицаЗначений");

Пока Запрос.Группировка(1) = 1 Цикл
    Док = Запрос.Реализация;
    Док.ВыгрузитьТабличнуюЧасть(ТЗ);
    
    ТЗ.ВыбратьСтроки();
    Пока ТЗ.ПолучитьСтроку() = 1 Цикл
        Сообщить("Товар: " + ТЗ.Номенклатура.Наименование + " | Количество: " + ТЗ.Количество);
    КонецЦикла;
КонецЦикла;

Способ 4. Использование объекта выборки документов вместо запроса

Если в алгоритме не требуется сложных агрегатных функций или условий по регистрам, а нужен простой хронологический перебор документов за период, можно отказаться от объекта Запрос и применить метод ВыбратьДокументы():


Док = СоздатьОбъект("Документ.Реализация");
Док.ВыбратьДокументы(ВыбНачПериода, ВыбКонПериода);

Пока Док.ПолучитьДокумент() = 1 Цикл
    Док.ВыбратьСтроки();
    Пока Док.ПолучитьСтроку() = 1 Цикл
        Сообщить("Документ № " + Док.НомерДок + " | Позиция: " + Док.Номенклатура.Наименование);
    КонецЦикла;
КонецЦикла;

Такой вариант сокращает объем используемой памяти и устраняет необходимость в создании промежуточных объектов при обходе выборки.

← На главную