Как правильно обойти выборку документов, получить табличную часть и сгруппировать данные без запроса в 1С

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия IT и автоматизация бизнеса
← На главную

При разработке отчетов и обработок в 1С начинающие программисты нередко сталкиваются с ошибками при работе с выборками документов. Типичная ситуация: требуется получить документы за период, прочитать строки их табличной части, вывести информацию в табличный документ или сгруппировать позиции номенклатуры, исключив дублирование строк. При этом на практике возникают исключения вида «Объект не определен» или «Итератор для значения не определен».

Разберем детально, почему возникают эти ошибки, как устроен механизм выборок платформы 1С:Предприятие и как решить задачу обхода и группировки данных без применения языка запросов и СКД.

Анализ типичных ошибок при обходе выборки документов

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


Выборка = Документы.РеализацияТоваровУслуг.Выбрать(ДатаНач, ДатаКон,,,);
Если Выборка.Проведен Тогда 
    Выборка.ПолучитьОбъект; 
    Для Каждого СтрокаВыборки ИЗ Выборка.Товары Цикл 
        // Заполнение параметров области
    КонецЦикла;
КонецЕсли;

В этом коде допущен ряд принципиальных ошибок архитектуры 1С:

  1. Отсутствие метода позиционирования выборки. Объект ВыборкаДокументов после создания методом Выбрать() указывает на позицию «до первой записи». Чтобы перейти к первой и последующим записям, необходимо использовать цикл Пока Выборка.Следующий() Цикл. Обращение к свойствам шапки до вызова метода Следующий() приведет к ошибке.
  2. Ошибочное использование метода ПолучитьОбъект(). Метод ПолучитьОбъект() является функцией, которая считывает данные из базы данных и возвращает новый объект прикладного типа (например, ДокументОбъект.РеализацияТоваровУслуг). Вызов вида Выборка.ПолучитьОбъект; без присвоения значения переменной просто считывает объект в память и сразу же его теряет. Сама выборка при этом не превращается в объект документа.
  3. Попытка чтения табличной части из выборки. Объект ВыборкаДокументов предоставляет доступ только к реквизитам шапки документа (ссылка, дата, номер, проведен и т.д.). Табличные части в выборку не загружаются. Обратиться к коллекции Товары можно только через ссылку (или через полученный объект документа).
  4. Неверная область видимости вывода в макет. Если метод ТабДок.Вывести(ОбластьСтрока) вызывается за пределами цикла перебора строк, в печатную форму попадет только одна последняя строка документа, а все предыдущие будут перезаписаны в параметрах и проигнорированы.

Исправление алгоритма обхода документов и вывода строк в макет

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


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

Пока Выборка.Следующий() Цикл
    
    // Проверяем признак проведения документа
    Если Не Выборка.Проведен Тогда
        Продолжить;
    КонецЕсли;
    
    // Получаем объект документа для доступа к табличной части
    ДокументОбъект = Выборка.ПолучитьОбъект();
    
    // Обходим строки табличной части документа
    Для Каждого СтрокаТЧ Из ДокументОбъект.Товары Цикл
        ОбластьСтрока.Параметры.НомерСтроки  = СтрокаТЧ.НомерСтроки;
        ОбластьСтрока.Параметры.Номенклатура = СтрокаТЧ.Номенклатура;
        ОбластьСтрока.Параметры.Количество   = СтрокаТЧ.Количество;
        ОбластьСтрока.Параметры.Цена         = СтрокаТЧ.Цена;
        ОбластьСтрока.Параметры.Сумма        = СтрокаТЧ.Сумма;
        
        // Выводим строку в табличный документ на каждой итерации
        ТабДок.Вывести(ОбластьСтрока);
    КонецЦикла;
    
КонецЦикла;

Обратим внимание: обращение к табличной части через Выборка.Ссылка.Товары или через ПолучитьОбъект() инициирует неявное считывание данных из СУБД для каждого отдельного документа. В реальных продуктивных системах такой подход приводит к серьезной нагрузке (проблема N+1 запросов), поэтому для массовых выборок в боевых задачах всегда рекомендуется использовать объект Запрос.

Группировка номенклатуры без запроса и СКД с помощью ТаблицыЗначений

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

Разберем по шагам, как решить эту задачу с помощью объекта ТаблицаЗначений и встроенного метода Свернуть():

  1. Создаем пустую таблицу значений на сервере и объявляем в ней необходимые колонки: номенклатура, количество, сумма.
  2. В цикле обхода документов выгружаем или построчно добавляем позиции табличной части в созданную таблицу значений.
  3. Применяем метод ТаблицаЗначений.Свернуть("КолонкиГруппировки", "КолонкиСуммирования"). В качестве группировки передаем "Номенклатура", а в качестве ресурсов для суммирования — "Количество, Сумма".
  4. Рассчитываем цену программно. Важный нюанс: колонку «Цена» нельзя суммировать, иначе стоимость товара сложится некорректно. Среднюю цену сгруппированной позиции вычисляют делением итоговой суммы на итоговое количество.
  5. Обходим полученную свернутую таблицу и выводим готовые строки в ТабличныйДокумент.

Посмотрим на практическую реализацию данного механизма:


&НаСервере
Функция СформироватьОтчетНаСервере() Экспорт

    ТабДок = Новый ТабличныйДокумент;
    Макет  = Отчет.ПолучитьМакет("ОтчетПоРеализации");
    
    ОбластьЗаголовок     = Макет.ПолучитьОбласть("Заголовок");
    ОбластьШапкаТаблицы = Макет.ПолучитьОбласть("ШапкаТаблицы");
    ОбластьСтрока        = Макет.ПолучитьОбласть("Строка");
    
    // Заполняем и выводим заголовок
    ОбластьЗаголовок.Параметры.НачалоПериода = Отчет.НачалоПериода;
    ОбластьЗаголовок.Параметры.КонецПериода  = Отчет.КонецПериода;
    ТабДок.Вывести(ОбластьЗаголовок);
    ТабДок.Вывести(ОбластьШапкаТаблицы);
    
    // Создаем промежуточную таблицу значений для накопления строк
    ТЗДанные = Новый ТаблицаЗначений;
    ТЗДанные.Колонки.Добавить("Номенклатура");
    ТЗДанные.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
    ТЗДанные.Колонки.Добавить("Сумма",      Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    // Выбираем документы за указанный период
    Выборка = Документы.РеализацияТоваровУслуг.Выбрать(Отчет.НачалоПериода, Отчет.КонецПериода);
    
    Пока Выборка.Следующий() Цикл
        Если Не Выборка.Проведен Тогда
            Продолжить;
        КонецЕсли;
        
        ДокОбъект = Выборка.ПолучитьОбъект();
        Для Каждого СтрТЧ Из ДокОбъект.Товары Цикл
            НоваяСтрока = ТЗДанные.Добавить();
            НоваяСтрока.Номенклатура = СтрТЧ.Номенклатура;
            НоваяСтрока.Количество   = СтрТЧ.Количество;
            НоваяСтрока.Сумма        = СтрТЧ.Сумма;
        КонецЦикла;
    КонецЦикла;
    
    // Сворачиваем строки: объединяем одинаковую номенклатуру и суммируем показатели
    ТЗДанные.Свернуть("Номенклатура", "Количество, Сумма");
    
    // Выводим сгруппированные данные в макет
    Номер = 1;
    Для Каждого СтрТЗ Из ТЗДанные Цикл
        ОбластьСтрока.Параметры.НомерСтроки  = Номер;
        ОбластьСтрока.Параметры.Номенклатура = СтрТЗ.Номенклатура;
        ОбластьСтрока.Параметры.Количество   = СтрТЗ.Количество;
        ОбластьСтрока.Параметры.Сумма        = СтрТЗ.Сумма;
        
        // Рассчитываем расчетную цену
        Если СтрТЗ.Количество <> 0 Тогда
            ОбластьСтрока.Параметры.Цена = Окр(СтрТЗ.Сумма / СтрТЗ.Количество, 2);
        Иначе
            ОбластьСтрока.Параметры.Цена = 0;
        КонецЕсли;
        
        ТабДок.Вывести(ОбластьСтрока);
        Номер = Номер + 1;
    КонецЦикла;
    
    Возврат ТабДок;

КонецФункции

Штатная работа с выбором периода на управляемой форме

Нередко у начинающих разработчиков возникает вопрос: как открыть стандартное окно выбора периода на форме? Простое создание структуры ПараметрыВыбора = Новый Структура(...) не вызывает никаких визуальных окон.

Для реализации интерактивного выбора диапазона дат на управляемой форме применяются следующие подходы:

  1. Использование реквизита с типом СтандартныйПериод. Если добавить на форму реквизит с типом СтандартныйПериод и перетащить его в элементы формы, платформа 1С автоматически сформирует поле ввода со стандартной кнопкой выбора. При нажатии на нее будет открываться встроенный календарь с быстрым выбором месяца, квартала или произвольного интервала.
  2. Программный вызов диалога. Если требуется открыть окно выбора периода по нажатию на отдельную команду формы, на клиенте создается объект ДиалогРедактированияСтандартногоПериода. С помощью метода Показать() открывается диалоговое окно, а результат выбора обрабатывается в процедуре оповещения.

Использование связки корректного обхода выборки через Пока ... Следующий() и метода Свернуть() позволяет быстро реализовывать простые алгоритмы обработки документов в памяти без необходимости задействовать сложные механизмы компоновки данных.

← На главную