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

Программист 1С v7.7 Управленческий учет Промышленность, строительство и АПК
← На главную

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

Рассмотрим проблему, с которой столкнулся автор темы: есть плоская таблица с данными, и попытка вывести её с группировками через один общий цикл Для приводит к тому, что итоги "съезжают", дублируются или не рассчитываются вовсе. Это происходит из-за сложности отслеживания момента смены группировок (начала нового проекта или статьи) внутри одного линейного прохода.

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

Почему не работает подход с одним циклом?

В исходном коде используется попытка отследить изменение значений (например, Если текПодр <> прПодр) внутри одного цикла перебора строк. Такой алгоритм называется "Control Break" (разрыв по условию). Он вполне работоспособен, но требует соблюдения строжайших условий, которые часто упускаются:

  1. Обязательная сортировка. Таблица должна быть идеально отсортирована в порядке вложенности группировок. Если сортировки нет, группы будут разрываться и повторяться многократно.
  2. Порядок условий. Проверять смену группировок нужно от старшей к младшей (сначала сменился ли Проект, потом Статья, потом Подразделение).
  3. Обработка "хвоста". После выхода из цикла нужно принудительно вывести итоги для последних групп, так как внутри цикла условие <> для последней строки уже не сработает.
  4. Сброс накопителей. Переменные, хранящие суммы по группам, нужно обнулять в строго определенный момент.

Из-за сложности отладки такого "монстра" в 1С 7.7 рекомендуется использовать более структурированные подходы.

Решение №1: Метод предварительной свертки таблиц

Этот метод, предложенный в обсуждении, является одним из самых надежных и простых для понимания. Идея заключается в том, чтобы не считать итоги "на лету" во время вывода, а заранее подготовить вспомогательные таблицы значений, где эти итоги уже посчитаны.

В платформе 1С 7.7 у объекта ТаблицаЗначений есть мощный метод Свернуть(). Он позволяет схлопнуть одинаковые строки, просуммировав числовые показатели.

Разберем алгоритм по шагам:

Шаг 1. Подготовка исходной таблицы

Предположим, у нас есть исходная таблица ТЗ_Исходная с колонками: Проект, Статья, Подразделение, Сумма, Количество.

Шаг 2. Создание таблиц для группировок

Нам нужно создать отдельные таблицы для каждого уровня группировки. Если у нас три уровня (Проект -> Статья -> Подразделение), нам понадобятся соответствующие таблицы.


// Создаем таблицу для верхнего уровня (Проекты)
ТЗ_Проекты = СоздатьОбъект("ТаблицаЗначений");
ТЗ_Исходная.Выгрузить(ТЗ_Проекты);
// Сворачиваем по Проекту, суммируем ресурсы
ТЗ_Проекты.Свернуть("Проект", "Сумма, Количество"); 
ТЗ_Проекты.Сортировать("Проект");

// Создаем таблицу для второго уровня (Проекты + Статьи)
ТЗ_Статьи = СоздатьОбъект("ТаблицаЗначений");
ТЗ_Исходная.Выгрузить(ТЗ_Статьи);
// Сворачиваем по паре Проект+Статья
ТЗ_Статьи.Свернуть("Проект, Статья", "Сумма, Количество");
ТЗ_Статьи.Сортировать("Проект, Статья");

// Исходная таблица будет служить детальными записями
ТЗ_Исходная.Сортировать("Проект, Статья, Подразделение");

Теперь у нас есть ТЗ_Проекты, где каждая строка — это уникальный проект с уже готовой общей суммой, и ТЗ_Статьи, где посчитаны суммы по статьям внутри проектов.

Шаг 3. Организация вложенных циклов вывода

Вместо одного цикла мы сделаем "матрешку". Это гарантирует, что структура отчета не нарушится.


// 1. Цикл по Проектам
ТЗ_Проекты.ВыбратьСтроки();
Пока ТЗ_Проекты.ПолучитьСтроку() = 1 Цикл
    
    // Запоминаем текущий проект
    ТекПроект = ТЗ_Проекты.Проект;
    
    // Выводим секцию с заголовком и итогами Проекта
    // Итоги берем прямо из ТЗ_Проекты - они там уже посчитаны!
    ПечСуммаПроекта = ТЗ_Проекты.Сумма;
    Таб.ВывестиСекцию("ЗаголовокПроекта"); 
    
    // 2. Цикл по Статьям (фильтруем только статьи текущего проекта)
    // В 7.7 нет простого отбора, поэтому перебираем и проверяем условие
    // Или используем метод НайтиЗначение, если знаем как оптимизировать
    
    ТЗ_Статьи.ВыбратьСтроки();
    Пока ТЗ_Статьи.ПолучитьСтроку() = 1 Цикл
        
        // Отсекаем чужие проекты
        Если ТЗ_Статьи.Проект <> ТекПроект Тогда
            Продолжить;
        КонецЕсли;
        
        ТекСтатья = ТЗ_Статьи.Статья;
        
        // Выводим секцию Статьи с готовыми итогами
        ПечСуммаСтатьи = ТЗ_Статьи.Сумма;
        Таб.ВывестиСекцию("ЗаголовокСтатьи");
        
        // 3. Цикл по детальным записям (Подразделениям)
        ТЗ_Исходная.ВыбратьСтроки();
        Пока ТЗ_Исходная.ПолучитьСтроку() = 1 Цикл
            
            // Фильтр: должны совпасть и Проект, и Статья
            Если (ТЗ_Исходная.Проект <> ТекПроект) ИЛИ (ТЗ_Исходная.Статья <> ТекСтатья) Тогда
                Продолжить;
            КонецЕсли;
            
            // Вывод детальной строки
            ПечПодразделение = ТЗ_Исходная.Подразделение;
            ПечСумма = ТЗ_Исходная.Сумма;
            Таб.ВывестиСекцию("Строка");
            
        КонецЦикла; // Конец деталей
        
        // Если нужен подвал статьи
        Таб.ВывестиСекцию("ПодвалСтатьи");
        
    КонецЦикла; // Конец статей

    // Если нужен подвал проекта
    Таб.ВывестиСекцию("ПодвалПроекта");

КонецЦикла; // Конец проектов

Преимущества метода:

Решение №2: Оптимизированный перебор с сортировкой

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

В 1С 7.7 этот метод часто реализуется через перебор строк и сравнение с предыдущим значением, но важно правильно расставить вывод секций.


// ОБЯЗАТЕЛЬНО сортируем таблицу
ТбЗн.Сортировать("Проект, ЭлементыЗатрат, Подразделения");

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

// Переменные для итогов
ИтогПроект = 0;
ИтогСтатья = 0;

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

    // Проверяем смену средней группировки (Статья)
    // Условие: Статья изменилась ИЛИ изменился Проект (выше)
    Если (ТбЗн.ЭлементыЗатрат <> ПредСтатья) И (ПустоеЗначение(ПредСтатья) = 0) Тогда
         Таб.ВывестиСекцию("ИтогСтатьи");
         ИтогСтатья = 0;
    КонецЕсли;
    
    // Если это начало новой группы - выводим заголовки
    Если ТбЗн.Проект <> ПредПроект Тогда
        ПечПроект = ТбЗн.Проект;
        Таб.ВывестиСекцию("ЗаголовокПроекта");
        ПредПроект = ТбЗн.Проект;
    КонецЕсли;
    
    Если ТбЗн.ЭлементыЗатрат <> ПредСтатья Тогда
        ПечСтатья = ТбЗн.ЭлементыЗатрат;
        Таб.ВывестиСекцию("ЗаголовокСтатьи");
        ПредСтатья = ТбЗн.ЭлементыЗатрат;
    КонецЕсли;

    // Выводим строку
    Таб.ВывестиСекцию("Строка");
    
    // Накапливаем итоги
    ИтогПроект = ИтогПроект + ТбЗн.Сумма;
    ИтогСтатья = ИтогСтатья + ТбЗн.Сумма;
    
КонецЦикла;

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

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

Рекомендации по форматированию данных

В вопросе автора использовался код СокрЛП(Формат(Сумма, стФорматС)). Это правильный подход для 1С 7.7. Напомним, что:

Использование объекта "Запрос"

Стоит также отметить, что если данные для таблицы берутся из регистров или документов, то самым профессиональным решением в 1С 7.7 является использование объекта Запрос.

Запрос автоматически умеет:

  1. Группировать данные (Группировка Проект;).
  2. Считать суммы (Функция СуммаС = Сумма(Сумма);).
  3. Сортировать выборку.

Обход результатов запроса аналогичен методу вложенных циклов, но работает быстрее, чем перебор ТаблицыЗначений.


Пока Запрос.Группировка("Проект") = 1 Цикл
    // Вывод шапки проекта
    Пока Запрос.Группировка("ЭлементыЗатрат") = 1 Цикл
         // Вывод шапки статьи
         Пока Запрос.Группировка("Подразделения") = 1 Цикл
             // Вывод строки
         КонецЦикла;
         // Вывод итога статьи (Запрос.СуммаС здесь содержит итог по текущей группировке)
    КонецЦикла;
    // Вывод итога проекта
КонецЦикла;

Заключение

Для исправления проблемы с циклами в отчете 1С 7.7, показанной в теме форума, наиболее эффективным решением будет отказ от одного сложного цикла с проверкой условий в пользу:

  1. Либо создания свернутых копий таблицы значений для каждого уровня группировки.
  2. Либо использования правильной структуры вложенных циклов с предварительной сортировкой исходной таблицы.

Оба метода гарантируют корректный расчет и вывод итоговых сумм по проектам, статьям и подразделениям.

← На главную