Как сгруппировать данные при выводе в макет 1С по условию?

Программист 1С v8.3 (Управляемые формы) 1С:Зарплата и Управление Персоналом Управленческий учет
← На главную

При формировании печатных форм и отчетов в 1С:Предприятии часто возникает задача группировки данных. Например, нам необходимо вывести список ведомостей, где для каждой ведомости сначала печатается её «шапка» (номер, дата, общие реквизиты), а затем — список сотрудников, относящихся исключительно к этой ведомости. При этом важно избежать дублирования заголовков для каждого сотрудника.

В этой статье мы подробно разберем типичные ошибки, которые совершают разработчики при попытке решить эту задачу, проанализируем некорректный код и рассмотрим два рабочих решения: классический метод сравнения значений (сохранение состояния) и профессиональный метод с использованием итогов в запросе и обхода по группировкам.

Анализ исходной проблемы и ошибок в коде

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


НПП = 0;
Пока ВыборкаВедомостиВБанк.Следующий() Цикл
    
    НПП = НПП + 1;
    Ведомость = ВыборкаВедомостиВБанк.Ссылка;
    
    ОбластьМакетаШапкаДокумента = Макет.ПолучитьОбласть("ШапкаДокумента");
    ОбластьМакетаСтрока         = Макет.ПолучитьОбласть("Строка");
    
    ОбластьМакетаШапкаДокумента.Параметры.ПериодРегистрации = "Список за " + Формат(ВыборкаВедомостиВБанк.Ссылка.Дата,"ДФ='MMMM yyyy'");                 
    ТабДок.Вывести(ОбластьМакетаШапкаДокумента);
    
    ОбластьМакетаСтрока.Параметры.НПП                = НПП;
    ОбластьМакетаСтрока.Параметры.Физлицо            = ВыборкаВедомостиВБанк.ФизическоеЛицо;
    ОбластьМакетаСтрока.Параметры.КВыплате           = ВыборкаВедомостиВБанк.КВыплате;
    
    ТабДок.Вывести(ОбластьМакетаСтрока);
    
КонецЦикла;

Выясним причину такого поведения. Данный цикл последовательно обходит плоскую таблицу результатов запроса. Поскольку команды вывода шапки ТабДок.Вывести(ОбластьМакетаШапкаДокумента) и строки ТабДок.Вывести(ОбластьМакетаСтрока) выполняются безусловно на каждой итерации цикла, система генерирует новую шапку для абсолютно каждого сотрудника. В итоге мы получаем чередование: «Шапка 1 — Сотрудник 1», «Шапка 1 — Сотрудник 2» (вместо того чтобы вывести одну шапку и под ней двух сотрудников).

В процессе обсуждения автор попытался внедрить алгоритм с запоминанием предыдущего значения ведомости (так называемый метод «контрольного перерыва» или сравнения с предыдущим значением). Однако в итоговом коде автора (Сообщение 12) содержатся три критические ошибки:

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

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


Решение №1: Метод сравнения значений (оптимизированный классический вариант)

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

Разберем рабочий и оптимизированный код по шагам:


// 1. Получаем области макета ОДИН раз перед циклом для экономии ресурсов
ОбластьМакетаШапкаДокумента = Макет.ПолучитьОбласть("ШапкаДокумента");
ОбластьМакетаСтрока         = Макет.ПолучитьОбласть("Строка");

СтароеЗначение = "";
НПП = 0;

Пока ВыборкаВедомостиВБанк.Следующий() Цикл
    
    ТекущееЗначение = ВыборкаВедомостиВБанк.Ссылка; // Сравниваем по ссылке на документ
    
    // Проверяем, изменилась ли ведомость по сравнению с предыдущей итерацией
    Если СтароеЗначение <> ТекущееЗначение Тогда 
        
        // Выводим шапку новой ведомости
        ОбластьМакетаШапкаДокумента.Параметры.ПериодРегистрации = "Список за " + Формат(ТекущееЗначение.Дата, "ДФ='MMMM yyyy'");                 
        ТабДок.Вывести(ОбластьМакетаШапкаДокумента);
        
        // Сбрасываем счетчик номеров по порядку для новой ведомости
        НПП = 0; 
        
        // Запоминаем текущую ведомость как старую
        СтароеЗначение = ТекущееЗначение;
        
    КонецЕсли;
    
    // Код вывода сотрудника выполняется ВСЕГДА, независимо от того, изменилась ведомость или нет
    НПП = НПП + 1;
    
    ОбластьМакетаСтрока.Параметры.НПП      = НПП;
    ОбластьМакетаСтрока.Параметры.Физлицо  = ВыборкаВедомостиВБанк.ФизическоеЛицо;
    ОбластьМакетаСтрока.Параметры.КВыплате = ВыборкаВедомостиВБанк.КВыплате;
    
    ТабДок.Вывести(ОбластьМакетаСтрока);
    
КонецЦикла;

Преимущества этого метода: Простая и понятная логика, не требующая сложной перестройки структуры запроса. Достаточно просто добавить условие на проверку изменения группировочного поля.

Важное ограничение: Этот метод будет работать стабильно только при условии, что данные в запросе жестко отсортированы по ведомости (секция УПОРЯДОЧИТЬ ПО Ссылка). Если сортировка собьется, СУБД может вернуть записи вразнобой (например: Ведомость1, Ведомость2, Ведомость1), что приведет к повторному ошибочному выводу шапки для Ведомости1.


Решение №2: Профессиональный подход через "Итоги" в запросе

В платформе 1С:Предприятие 8 реализован мощный встроенный механизм для работы с иерархическими и группировочными данными — использование предложения ИТОГИ ПО в тексте запроса и последующий обход выборки по группировкам. Это наиболее надежный и производительный стандарт разработки.

Рассмотрим подробнее данный метод.

Шаг 1. Модернизация текста запроса. В конец вашего существующего запроса необходимо добавить конструкцию расчета итогов по полю ведомости:


ВЫБРАТЬ
    ВедомостьВБанкСотрудники.Ссылка КАК Ссылка,
    ВедомостьВБанкСотрудники.ФизическоеЛицо КАК ФизическоеЛицо,
    ВедомостьВБанкСотрудники.КВыплате КАК КВыплате
ИЗ
    Документ.ВедомостьВБанк.Сотрудники КАК ВедомостьВБанкСотрудники
УПОРЯДОЧИТЬ ПО
    Ссылка
ИТОГИ ПО
    Ссылка

Шаг 2. Обход результатов запроса с помощью вложенных циклов. Для вывода сгруппированных данных мы используем два цикла: внешний обходит сами ведомости, а внутренний — сотрудников, привязанных к текущей ведомости.


// Получаем области макета один раз до начала вывода
ОбластьМакетаШапкаДокумента = Макет.ПолучитьОбласть("ШапкаДокумента");
ОбластьМакетаСтрока         = Макет.ПолучитьОбласть("Строка");

РезультатЗапроса = Запрос.Выполнить();

// Выбираем данные верхнего уровня группировки (только Ведомости)
ВыборкаВедомость = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

Пока ВыборкаВедомость.Следующий() Цикл
    
    // На этом уровне нам доступны реквизиты самой ведомости
    ОбластьМакетаШапкаДокумента.Параметры.ПериодРегистрации = "Список за " + Формат(ВыборкаВедомость.Ссылка.Дата, "ДФ='MMMM yyyy'");        
    ТабДок.Вывести(ОбластьМакетаШапкаДокумента);
    
    НПП = 0; // Сбрасываем нумерацию сотрудников для новой ведомости
    
    // Получаем подчиненную выборку детальных записей (сотрудники текущей ведомости)
    ВыборкаДетали = ВыборкаВедомость.Выбрать();
    
    Пока ВыборкаДетали.Следующий() Цикл
        
        НПП = НПП + 1;
        
        // Заполняем параметры строки макета из детальной выборки
        ОбластьМакетаСтрока.Параметры.НПП      = НПП;
        ОбластьМакетаСтрока.Параметры.Физлицо  = ВыборкаДетали.ФизическоеЛицо;
        ОбластьМакетаСтрока.Параметры.КВыплате = ВыборкаДетали.КВыплате;
        
        ТабДок.Вывести(ОбластьМакетаСтрока);
        
    КонецЦикла;
    
КонецЦикла;

Почему этот подход лучше:

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

← На главную