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

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

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

Ограничения объекта ВыборкаИзРезультатаЗапроса и получение Таблицы Значений

Объект ВыборкаИзРезультатаЗапроса предназначен для последовательного обхода строк и не имеет встроенного метода Выгрузить(). Метод Выгрузить() доступен исключительно у объекта РезультатЗапроса.

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

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


// Создаем таблицу значений с нужной структурой колонок
ТЗ_Данные = Новый ТаблицаЗначений;
ТЗ_Данные.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ_Данные.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
ТЗ_Данные.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число"));

// Заполняем таблицу построчно из выборки
Пока ВыборкаЗапасы.Следующий() Цикл
    НоваяСтрока = ТЗ_Данные.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, ВыборкаЗапасы);
КонецЦикла;

// При необходимости вернуть указатель выборки в исходное состояние
ВыборкаЗапасы.Сбросить();

Метод Сбросить() возвращает позицию курсора выборки в начало (перед первой записью), что позволяет повторно обойти ту же выборку в дальнейшем коде.

Определение последней строки выборки и вывод общих итогов

Если цель выгрузки в таблицу значений заключалась только в том, чтобы узнать, когда обход подошел к концу, использовать промежуточную таблицу не требуется. Рассмотрим два подхода:

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

Счетчик = 0;
ВсегоСтрок = ВыборкаЗапасы.Количество();

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

// Вывод общих итогов по документу после завершения цикла
ТабДок.Вывести(ОбластьИтогиДокумента);

Штатный вывод иерархических разделов и итогов через запрос

Наиболее производительный и правильный с точки зрения стандартов 1С способ вывода печатной формы с разделителями по номенклатурным группам — перенос группировок и суммирования в текст запроса с помощью секции ИТОГИ ПО.

Посмотрим на пример запроса и многоуровневого обхода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Запасы.Номенклатура.НоменклатурнаяГруппа КАК НоменклатурнаяГруппа,
    |	Запасы.Номенклатура КАК Номенклатура,
    |	Запасы.Количество КАК Количество,
    |	Запасы.Сумма КАК Сумма
    |ИЗ
    |	Документ.КоммерческоеПредложениеКлиенту.Запасы КАК Запасы
    |ГДЕ
    |	Запасы.Ссылка = &Ссылка
    |ИТОГИ
    |	СУММА(Сумма),
    |	СУММА(Количество)
    |ПО
    |	ОБЩИЕ,
    |	НоменклатурнаяГруппа";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
Результат = Запрос.Выполнить();

// 1. Выборка общих итогов
ВыборкаОбщие = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Если ВыборкаОбщие.Следующий() Тогда
    
    // 2. Выборка по разделам (Номенклатурным группам)
    ВыборкаГруппы = ВыборкаОбщие.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
    Пока ВыборкаГруппы.Следующий() Цикл
        
        // Выводим шапку группы/раздела
        ОбластьШапкаГруппы.Параметры.Заполнить(ВыборкаГруппы);
        ТабДок.Вывести(ОбластьШапкаГруппы);
        
        // 3. Выборка детальных строк текущей группы
        ВыборкаДетали = ВыборкаГруппы.Выбрать();
        Пока ВыборкаДетали.Следующий() Цикл
            ОбластьСтрока.Параметры.Заполнить(ВыборкаДетали);
            ТабДок.Вывести(ОбластьСтрока);
        КонецЦикла;
        
        // Выводим подвал раздела с итогами по текущей группе
        ОбластьПодвалГруппы.Параметры.Заполнить(ВыборкаГруппы);
        ТабДок.Вывести(ОбластьПодвалГруппы);
        
    КонецЦикла;
    
    // Выводим общий подвал документа с итогами
    ОбластьПодвалДокумента.Параметры.Заполнить(ВыборкаОбщие);
    ТабДок.Вывести(ОбластьПодвалДокумента);
    
КонецЕсли;

Вывод итогов разделов в линейной выборке (без изменения запроса)

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


ТекущаяГруппа = Неопределено;
СуммаПоГруппе = 0;

Пока ВыборкаЗапасы.Следующий() Цикл
    
    // Проверяем смену номенклатурной группы
    Если ВыборкаЗапасы.НоменклатурнаяГруппа <> ТекущаяГруппа Тогда
        
        // Если это не первая группа, закрываем предыдущий раздел
        Если ТекущаяГруппа <> Неопределено Тогда
            ОбластьПодвалГруппы.Параметры.ИтогСумма = СуммаПоГруппе;
            ТабДок.Вывести(ОбластьПодвалГруппы);
        КонецЕсли;
        
        // Открываем новый раздел
        ТекущаяГруппа = ВыборкаЗапасы.НоменклатурнаяГруппа;
        СуммаПоГруппе = 0;
        
        ОбластьШапкаГруппы.Параметры.Группа = ТекущаяГруппа;
        ТабДок.Вывести(ОбластьШапкаГруппы);
        
    КонецЕсли;
    
    // Накапливаем сумму и выводим детальную строку
    СуммаПоГруппе = СуммаПоГруппе + ВыборкаЗапасы.Сумма;
    ОбластьСтрока.Параметры.Заполнить(ВыборкаЗапасы);
    ТабДок.Вывести(ОбластьСтрока);
    
КонецЦикла;

// Закрываем подвал самого последнего раздела
Если ТекущаяГруппа <> Неопределено Тогда
    ОбластьПодвалГруппы.Параметры.ИтогСумма = СуммаПоГруппе;
    ТабДок.Вывести(ОбластьПодвалГруппы);
КонецЕсли;

Исправление ошибки «Слишком много фактических параметров» в методе НайтиСтроки

При поиске в таблице значений часто допускается ошибка передачи условий двумя отдельными аргументами. Метод НайтиСтроки() принимает ровно один параметр — объект Структура, где ключи соответствуют именам колонок, а значения — искомым данным.

Ошибочный вызов:


// Приведет к ошибке: "Слишком много фактических параметров"
МассивСтрок = ТЗ_Итоги.НайтиСтроки("Разделитель", ВыборкаЗапасы.Номенклатура.НоменклатурнаяГруппа);

Корректный вызов:


Отбор = Новый Структура("Разделитель", ВыборкаЗапасы.Номенклатура.НоменклатурнаяГруппа);
МассивСтрок = ТЗ_Итоги.НайтиСтроки(Отбор);

Использование запросов с секцией ИТОГИ ПО или конструкции ВЫБОР КОГДА для целевых сумм полностью исключает необходимость ручного поиска в таблицах значений и подсчета сумм во встроенном языке, что значительно ускоряет формирование печатных форм.

← На главную