При разработке отчетов в 1С:Предприятие 7.7 часто возникает задача вывести на печать данные из ТаблицыЗначений, сгруппировав их по одному или нескольким полям с подведением итогов. Начинающие разработчики нередко пытаются решить эту задачу с помощью вложенных циклов, что приводит к неверному результату, поскольку внутренний цикл сбивает указатель текущей строки во внешнем.
Рассмотрим классический и наиболее правильный подход к решению этой задачи, известный как «метод на излом» или «сравнение с предыдущим».
В исходном коде автора была предпринята попытка использовать два вложенных цикла Пока ТЗ.ПолучитьСтроку() = 1 Цикл. Проблема такого подхода в том, что оба цикла работают с одним и тем же итератором (указателем текущей строки) таблицы значений. Когда внутренний цикл доходит до конца таблицы, внешний цикл также завершается, так как метод ПолучитьСтроку() вернет 0. В итоге обработка данных происходит некорректно, и часть строк пропускается.
Правильное решение всегда строится на одном цикле обхода таблицы.
Суть метода заключается в том, чтобы перед выводом отсортировать таблицу значений по полям группировки, а затем в одном цикле обходить ее, отслеживая момент смены значения в этих полях. Когда значение меняется — это сигнал о том, что одна группа закончилась и началась другая. В этот момент нужно вывести итоги по завершенной группе и заголовок для новой.
Разберем алгоритм по шагам на примере группировки по полям «Расходы» и «СчетСписания».
// Сортируем ТЗ сначала по Расходам, затем по СчетуСписания
ТЗ.Сортировать("Расходы+,СчетСписания+");
Перем ПредыдущиеРасходы, ПредыдущийСчет;
Перем СуммаПоСчету, СуммаПоРасходам, ОбщаяСумма;
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
// ... здесь будет основная логика ...
КонецЦикла;
Давайте соберем все шаги в единый, работающий код. Предположим, что у нас есть макет с секциями: «ЗаголовокРасходов», «ЗаголовокСчета», «Строка», «ИтогоСчетСписания», «ИтогоРасходы», «ИтогоВсего».
Процедура ПечатьТаблицыЗначений(ТЗ, Таб)
// 1. Предварительная сортировка - ОБЯЗАТЕЛЬНО!
ТЗ.Сортировать("Расходы+,СчетСписания+");
// 2. Инициализация переменных для итогов
СуммаПоСчету = 0;
СуммаПоРасходам = 0;
ОбщаяСумма = 0;
// Переменные для хранения значений предыдущей строки
ПредыдущиеРасходы = "";
ПредыдущийСчет = "";
// 3. Начинаем обход таблицы в одном цикле
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
// 4. Проверяем смену группировок ПЕРЕД выводом строки
// Проверка смены самой верхней группы ("Расходы")
Если (ПредыдущиеРасходы <> ТЗ.Расходы) И (ТЗ.НомерСтроки() > 1) Тогда
// Если Расходы изменились, значит, и вложенная группа "Счет" тоже закончилась.
// Выводим итоги в обратном порядке: от вложенной к внешней.
Таб.ПрисоединитьСекцию("ИтогоСчетСписания");
Таб.ПрисоединитьСекцию("ИтогоРасходы");
// Сбрасываем накопители
СуммаПоСчету = 0;
СуммаПоРасходам = 0;
ИначеЕсли (ПредыдущийСчет <> ТЗ.СчетСписания) И (ТЗ.НомерСтроки() > 1) Тогда
// Расходы те же, но изменился Счет
Таб.ПрисоединитьСекцию("ИтогоСчетСписания");
СуммаПоСчету = 0;
КонецЕсли;
// 5. Выводим заголовки для НОВЫХ групп
// Если сменились Расходы, выводим новый заголовок
Если ПредыдущиеРасходы <> ТЗ.Расходы Тогда
Таб.ПрисоединитьСекцию("ЗаголовокРасходов");
ПредыдущиеРасходы = ТЗ.Расходы;
КонецЕсли;
// Если сменился Счет, выводим новый заголовок
Если ПредыдущийСчет <> ТЗ.СчетСписания Тогда
Таб.ПрисоединитьСекцию("ЗаголовокСчета");
ПредыдущийСчет = ТЗ.СчетСписания;
КонецЕсли;
// 6. Выводим строку детализации
Таб.ПрисоединитьСекцию("Строка");
// 7. Накапливаем суммы
СуммаПоСчету = СуммаПоСчету + ТЗ.Сумма;
СуммаПоРасходам = СуммаПоРасходам + ТЗ.Сумма;
ОбщаяСумма = ОбщаяСумма + ТЗ.Сумма;
КонецЦикла;
// 8. Вывод "подвалов" для самой последней группы после окончания цикла
Если ТЗ.КоличествоСтрок() > 0 Тогда
Таб.ПрисоединитьСекцию("ИтогоСчетСписания");
Таб.ПрисоединитьСекцию("ИтогоРасходы");
КонецЕсли;
// Вывод общего итога по всему отчету
Таб.ПрисоединитьСекцию("ИтогоВсего");
КонецПроцедуры
Важные моменты в коде:
ТЗ.НомерСтроки() > 1 нужна, чтобы не выводить итоги перед самой первой строкой таблицы.СуммаПоСчету) используется метод Таб.Параметры.Установить("ИмяПараметра", Значение) перед вызовом ПрисоединитьСекцию или ВывестиСекцию, либо в макете задаются выражения для ячеек. Этот код для краткости опущен.Хотя метод «на излом» является базовым и универсальным, в 1С 7.7 существуют и другие, более продвинутые способы, которые могут упростить код:
Запрос. Если данные можно получить напрямую из базы, то можно использовать запрос с конструкцией Группировка. Это позволит получить уже сгруппированные и посчитанные данные, что сильно упрощает их последующий вывод.ИндексированнаяТаблица. Он позволял эффективно группировать и фильтровать данные без необходимости физической сортировки исходной таблицы.ТЗ.Свернуть()). Затем обходить свернутую таблицу, а для вывода строк детализации находить нужные записи в исходной таблице. Этот метод может быть менее производительным на больших объемах данных.Несмотря на наличие альтернатив, понимание и умение применять классический алгоритм обхода отсортированной таблицы является фундаментальным навыком для любого программиста 1С 7.7.