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