При разработке отчетов и обработок в среде 1С:Предприятие 8 программисты часто сталкиваются с необходимостью вывода данных в иерархическом виде или с группировкой по определенным полям. Одной из наиболее распространенных проблем является «потеря» строк при обходе результата запроса. Разберем ситуацию, когда запрос возвращает все необходимые данные, но в итоговую печатную форму или таблицу попадает лишь часть из них. Проанализируем причины возникновения таких ошибок и изучим правильные методы работы с объектом РезультатЗапроса.
Рассмотрим типичный сценарий. Программист строит запрос с использованием конструкции ИТОГИ и пытается обойти его по группировкам. В коде это выглядит примерно так:
РезультатЗапроса = Запрос.Выполнить();
ВыборкаМашина = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Машина");
Пока ВыборкаМашина.Следующий() Цикл
// Вывод области заголовка группы
ОбластьМашина = Макет.ПолучитьОбласть("Машина");
ОбластьМашина.Параметры.Заполнить(ВыборкаМашина);
ТабДок.Вывести(ОбластьМашина);
// Переход к детальным записям
ВыборкаДетальныеЗаписи = ВыборкаМашина.Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Вывод строк
ОбластьСтрока.Параметры.Заполнить(ВыборкаДетальныеЗаписи);
ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;
КонецЦикла;
Выясним причину, по которой при наличии двух «Машин» в результате запроса, код обрабатывает только одну. Основная ошибка кроется в невнимательности при использовании метода Следующий(). Если внутри цикла по какой-то причине вызывается ВыборкаМашина.Следующий() еще раз (например, для проверки или по ошибке копирования кода), курсор выборки перескакивает через одну запись. В итоге итерация цикла завершается, а следующая запись уже пропущена.
В тексте запроса автора темы одновременно присутствуют предложения СГРУППИРОВАТЬ ПО и ИТОГИ. Проанализируем, как они взаимодействуют. Предложение СГРУППИРОВАТЬ ПО выполняется на уровне СУБД и «схлопывает» одинаковые строки, вычисляя агрегатные функции (такие как СУММА, МАКСИМУМ и др.). Это уменьшает объем передаваемых данных. Предложение ИТОГИ, напротив, выполняется платформой 1С и создает дерево итогов.
Важно помнить: если мы используем ИТОГИ ПО Машина ИЕРАРХИЯ, платформа сформирует выборку, содержащую:
Выбрать()).Рассмотрим структуру запроса более детально:
|ВЫБРАТЬ
| Вт_1.Подразделение,
| Вт_1.Номенклатура,
| Вт_1.Машина
|ИЗ
| Вт_1 КАК Вт_1
|ИТОГИ
| СУММА(План),
| СУММА(Факт)
|ПО
| Машина ИЕРАРХИЯ
Здесь поле Машина является ключевым для построения иерархического обхода. Использование ключевого слова ИЕРАРХИЯ позволяет автоматически включать в выборку всех родителей текущего элемента. Если иерархия не требуется, достаточно просто указать ПО Машина.
Для корректного вывода данных по группировкам необходимо строго следовать иерархии вызовов метода Выбрать(). Разберем по шагам, как это работает:
Выбрать у объекта РезультатЗапроса. Здесь мы указываем тип обхода ОбходРезультатаЗапроса.ПоГруппировкам и имя первой группировки — "Машина".Пока Выборка.Следующий(). Внутри этого цикла нам доступны значения итогов по данной конкретной машине.Выбрать() уже у объекта текущей выборки (ВыборкаМашина).Посмотрим на пример правильной реализации без лишних переходов:
Результат = Запрос.Выполнить();
// Выбираем только уровень группировки "Машина"
ВыборкаГрупп = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Машина");
Пока ВыборкаГрупп.Следующий() Цикл
// Здесь мы находимся на уровне ИТОГА по Машине
// Можно вывести заголовок группы в табличный документ
Сообщить("Обработка машины: " + ВыборкаГрупп.Машина);
// Получаем вложенную выборку (детальные записи для ЭТОЙ машины)
ВыборкаДетали = ВыборкаГрупп.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
// Здесь мы работаем с конкретной строкой
Сообщить(" Номенклатура: " + ВыборкаДетали.Номенклатура + " Факт: " + ВыборкаДетали.Факт);
КонецЦикла;
КонецЦикла;
Важный нюанс: Если в методе Выбрать() (для ВыборкаГрупп) не указать имя группировки "Машина", а в запросе несколько уровней итогов, то 1С начнет обход с самого верхнего уровня — с общего итога по всему запросу. Это также является частой причиной того, что «цикл срабатывает один раз и всё».
Если используется ИЕРАРХИЯ, структура выборки усложняется. Проанализируем ситуацию: машина может находиться в папке «Цех №1», которая в свою очередь находится в папке «Завод». При обходе ПоГруппировкам с иерархией, первой в выборку попадет запись «Завод», затем «Цех №1», и только потом конкретная машина.
В таких случаях полезно использовать свойство Выборка.ТипЗаписи(). Оно позволяет определить, является ли текущая строка итогом по группировке или детальной записью. Также метод Выборка.Уровень() поможет понять глубину вложенности для визуального оформления отчета (например, добавления отступов в ТабличныйДокумент).
Обратим внимание на важный аспект из примера кода: использование функции ЕСТЬNULL в тексте запроса. Проанализируем ситуацию: если в результате левого соединения или при отсутствии данных в поле попадает NULL, агрегатные функции (СУММА) в итогах могут отработать не так, как ожидает разработчик.
Рекомендуется: всегда преобразовывать NULL в 0 (для чисел) или в пустую строку/дату еще на этапе формирования временных таблиц или выборки. В исходном запросе это сделано верно: СУММА(ЕСТЬNULL(ОтчетМастераЗаСменуПолуфабрикаты.План, 0)). Это гарантирует, что на этапе формирования итогов платформа будет оперировать корректными числами, и итоговые суммы по машинам совпадут с суммой детальных записей.
Следующий() в условиях Если, если далее следует цикл Пока по этой же выборке.Выборка) для внешнего и внутреннего циклов. Это приведет к тому, что внутренний цикл «исчерпает» выборку внешнего.РезультатЗапроса.Выбрать() всегда лучше явно указывать имя группировки вторым параметром, чтобы код был читаемым и предсказуемым.ИТОГИ ПО..., обязательно должны присутствовать в списке ВЫБРАТЬ. В противном случае платформа выдаст ошибку исполнения запроса.Подводя итог, можно сказать, что механизм обхода по группировкам в 1С — это мощный инструмент для построения сложных отчетов. Главное — строго соблюдать вложенность выборок и следить за состоянием курсора выборки, не допуская лишних переходов между записями.