Как вывести на печать Таблицу Значений с группировкой в 1С 7.7?

Программист 1С v7.7
← На главную

При разработке отчетов в 1С:Предприятие 7.7 часто возникает задача вывести на печать данные из ТаблицыЗначений, сгруппировав их по одному или нескольким полям с подведением итогов. Начинающие разработчики нередко пытаются решить эту задачу с помощью вложенных циклов, что приводит к неверному результату, поскольку внутренний цикл сбивает указатель текущей строки во внешнем.

Рассмотрим классический и наиболее правильный подход к решению этой задачи, известный как «метод на излом» или «сравнение с предыдущим».

Анализ проблемы: почему вложенные циклы не работают

В исходном коде автора была предпринята попытка использовать два вложенных цикла Пока ТЗ.ПолучитьСтроку() = 1 Цикл. Проблема такого подхода в том, что оба цикла работают с одним и тем же итератором (указателем текущей строки) таблицы значений. Когда внутренний цикл доходит до конца таблицы, внешний цикл также завершается, так как метод ПолучитьСтроку() вернет 0. В итоге обработка данных происходит некорректно, и часть строк пропускается.

Правильное решение всегда строится на одном цикле обхода таблицы.

Основной алгоритм: Сортировка и один цикл

Суть метода заключается в том, чтобы перед выводом отсортировать таблицу значений по полям группировки, а затем в одном цикле обходить ее, отслеживая момент смены значения в этих полях. Когда значение меняется — это сигнал о том, что одна группа закончилась и началась другая. В этот момент нужно вывести итоги по завершенной группе и заголовок для новой.

Разберем алгоритм по шагам на примере группировки по полям «Расходы» и «СчетСписания».

  1. Сортировка. Это самый важный предварительный шаг. Без него данные будут идти вразнобой, и логика группировки не сработает. Сортируем таблицу по полям от внешней группировки к внутренней.
    
    // Сортируем ТЗ сначала по Расходам, затем по СчетуСписания
    ТЗ.Сортировать("Расходы+,СчетСписания+");
    
  2. Инициализация переменных. Перед началом цикла нам понадобятся переменные для хранения значений группировочных полей из предыдущей строки, а также переменные-накопители для сумм.
    
    Перем ПредыдущиеРасходы, ПредыдущийСчет;
    Перем СуммаПоСчету, СуммаПоРасходам, ОбщаяСумма;
    
  3. Один цикл. Организуем обход всей таблицы с помощью одного цикла.
    
    ТЗ.ВыбратьСтроки();
    Пока ТЗ.ПолучитьСтроку() = 1 Цикл
        // ... здесь будет основная логика ...
    КонецЦикла;
    
  4. Логика внутри цикла. На каждой итерации, перед выводом текущей строки, нужно проверить, не изменились ли значения в полях группировки по сравнению с предыдущей строкой. Проверку делаем от самой общей группы к самой вложенной.
  5. Вывод итогов после цикла. Когда цикл завершится, в переменных-накопителях останутся суммы по самой последней группе. Их нужно принудительно вывести, так как смена группы уже не произойдет.

Пример правильного кода с комментариями

Давайте соберем все шаги в единый, работающий код. Предположим, что у нас есть макет с секциями: «ЗаголовокРасходов», «ЗаголовокСчета», «Строка», «ИтогоСчетСписания», «ИтогоРасходы», «ИтогоВсего».


Процедура ПечатьТаблицыЗначений(ТЗ, Таб)

    // 1. Предварительная сортировка - ОБЯЗАТЕЛЬНО!
    ТЗ.Сортировать("Расходы+,СчетСписания+");

    // 2. Инициализация переменных для итогов
    СуммаПоСчету = 0;
    СуммаПоРасходам = 0;
    ОбщаяСумма = 0;

    // Переменные для хранения значений предыдущей строки
    ПредыдущиеРасходы = "";
    ПредыдущийСчет = "";

    // 3. Начинаем обход таблицы в одном цикле
    ТЗ.ВыбратьСтроки();
    Пока ТЗ.ПолучитьСтроку() = 1 Цикл
    
        // 4. Проверяем смену группировок ПЕРЕД выводом строки

        // Проверка смены самой верхней группы ("Расходы")
        Если (ПредыдущиеРасходы <> ТЗ.Расходы) И (ТЗ.НомерСтроки() > 1) Тогда
            // Если Расходы изменились, значит, и вложенная группа "Счет" тоже закончилась.
            // Выводим итоги в обратном порядке: от вложенной к внешней.
            Таб.ПрисоединитьСекцию("ИтогоСчетСписания");
            Таб.ПрисоединитьСекцию("ИтогоРасходы");
            
            // Сбрасываем накопители
            СуммаПоСчету = 0;
            СуммаПоРасходам = 0;
        ИначеЕсли (ПредыдущийСчет <> ТЗ.СчетСписания) И (ТЗ.НомерСтроки() > 1) Тогда
            // Расходы те же, но изменился Счет
            Таб.ПрисоединитьСекцию("ИтогоСчетСписания");
            СуммаПоСчету = 0;
        КонецЕсли;

        // 5. Выводим заголовки для НОВЫХ групп

        // Если сменились Расходы, выводим новый заголовок
        Если ПредыдущиеРасходы <> ТЗ.Расходы Тогда
            Таб.ПрисоединитьСекцию("ЗаголовокРасходов");
            ПредыдущиеРасходы = ТЗ.Расходы;
        КонецЕсли;

        // Если сменился Счет, выводим новый заголовок
        Если ПредыдущийСчет <> ТЗ.СчетСписания Тогда
            Таб.ПрисоединитьСекцию("ЗаголовокСчета");
            ПредыдущийСчет = ТЗ.СчетСписания;
        КонецЕсли;

        // 6. Выводим строку детализации
        Таб.ПрисоединитьСекцию("Строка");

        // 7. Накапливаем суммы
        СуммаПоСчету = СуммаПоСчету + ТЗ.Сумма;
        СуммаПоРасходам = СуммаПоРасходам + ТЗ.Сумма;
        ОбщаяСумма = ОбщаяСумма + ТЗ.Сумма;

    КонецЦикла;

    // 8. Вывод "подвалов" для самой последней группы после окончания цикла
    Если ТЗ.КоличествоСтрок() > 0 Тогда
        Таб.ПрисоединитьСекцию("ИтогоСчетСписания");
        Таб.ПрисоединитьСекцию("ИтогоРасходы");
    КонецЕсли;

    // Вывод общего итога по всему отчету
    Таб.ПрисоединитьСекцию("ИтогоВсего");

КонецПроцедуры

Важные моменты в коде:

Альтернативные подходы

Хотя метод «на излом» является базовым и универсальным, в 1С 7.7 существуют и другие, более продвинутые способы, которые могут упростить код:

  1. Использование объекта Запрос. Если данные можно получить напрямую из базы, то можно использовать запрос с конструкцией Группировка. Это позволит получить уже сгруппированные и посчитанные данные, что сильно упрощает их последующий вывод.
  2. Внешние компоненты. Часто для сложных отчетов использовали компоненту 1С++, которая предоставляла объект ИндексированнаяТаблица. Он позволял эффективно группировать и фильтровать данные без необходимости физической сортировки исходной таблицы.
  3. Свертка таблицы. Можно создать копию таблицы, свернуть ее по полям группировок с суммированием нужных колонок (ТЗ.Свернуть()). Затем обходить свернутую таблицу, а для вывода строк детализации находить нужные записи в исходной таблице. Этот метод может быть менее производительным на больших объемах данных.

Несмотря на наличие альтернатив, понимание и умение применять классический алгоритм обхода отсортированной таблицы является фундаментальным навыком для любого программиста 1С 7.7.

← На главную