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

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

При формировании отчетов или печатных форм часто возникает задача сгруппировать данные по какому-либо признаку. Классический пример — вывести список сотрудников, сгруппированный по их должностям. Начинающие разработчики нередко сталкиваются с ситуацией, когда в итоговый отчет попадают данные только по последней группе или все данные выводятся одной "сплошной" строкой. Рассмотрим, как избежать этой распространенной логической ошибки.

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

Анализ проблемы: почему в отчет попадает только последняя группа?

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

Вот упрощенный проблемный участок кода из обсуждения на форуме:


// Переменные инициализируются ОДИН РАЗ перед всеми циклами
СтрокаМассива = "";
СтрокаМассива1 = "";

// Получаем уникальный список должностей
СписокДолжностей = КадровыеДанные.Скопировать(, "ТекущаяДолжность");
СписокДолжностей.Свернуть("ТекущаяДолжность");
СписокДолжностей = СписокДолжностей.ВыгрузитьКолонку("ТекущаяДолжность");

// Внешний цикл по должностям
Для Каждого Должность Из СписокДолжностей Цикл

    // Получаем сотрудников текущей должности
    Отбор = Новый Структура("ТекущаяДолжность", Должность);
    МассивСотрудников = КадровыеДанные.НайтиСтроки(Отбор);
    
    // Внутренний цикл по сотрудникам
    Для Каждого СтрокаСотр Из МассивСотрудников Цикл
        
        // ... (код получения представлений сотрудника и должности)
        
        // Данные постоянно ДОБАВЛЯЮТСЯ в одну и ту же переменную
        СтрокаМассива = СтрокаМассива + ", " + ПредставлениеИмени;
        СтрокаМассива1 = СтрокаМассива1 + "-"+ПредставлениеДолжности + ":" + ПредоставлениеИмениИн + Символы.ПС;
        
    КонецЦикла;
    
КонецЦикла;

// Вывод результата происходит ПОСЛЕ того, как все циклы завершились
ДанныеОбъекта.Вставить("СтрокаСотрудников", СтрокаМассива);
ДанныеОбъекта.Вставить("СотИнФИО", СтрокаМассива1);

В чем заключается ошибка?

Переменные СтрокаМассива и СтрокаМассива1, которые должны накапливать информацию о сотрудниках, инициализируются (очищаются) только один раз — перед началом внешнего цикла по должностям.

  1. Когда цикл начинает обрабатывать первую должность (например, "Инженер"), он собирает в эти переменные всех инженеров.
  2. Когда цикл переходит ко второй должности (например, "Бухгалтер"), он не очищает эти переменные, а продолжает дописывать в них данные о бухгалтерах.

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

Решение: правильная организация циклов и инициализация переменных

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

Разберем правильную структуру кода по шагам:

Шаг 1. Подготовка данных и итоговой переменной

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


// ... (код получения таблицы КадровыеДанные) ...

// Получаем уникальный список должностей
СписокДолжностей = КадровыеДанные.Скопировать(, "ТекущаяДолжность");
СписокДолжностей.Свернуть("ТекущаяДолжность");
СписокДолжностей = СписокДолжностей.ВыгрузитьКолонку("ТекущаяДолжность");

// Основная переменная для хранения всего результата
ИтоговаяСтрокаВывода = "";

Шаг 2. Организация циклов и инициализация групповых переменных

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


// Внешний цикл по уникальным должностям
Для Каждого Должность Из СписокДолжностей Цикл
    
    // Получаем всех сотрудников для текущей должности
    Отбор = Новый Структура("ТекущаяДолжность", Должность);
    МассивСотрудниковПоДолжности = КадровыеДанные.НайтиСтроки(Отбор);
    
    // !!! ВАЖНО: Инициализируем (сбрасываем) переменные для КАЖДОЙ новой группы
    СтрокаСотрудниковТекущейДолжности = "";
    ПервыйСотрудникВГруппе = Истина;

    // Внутренний цикл по сотрудникам одной должности
    Для Каждого СтрокаСотр Из МассивСотрудниковПоДолжности Цикл
        
        // ... (далее код формирования строки) ...
        
    КонецЦикла;
    
    // ... (далее код добавления результата группы в итоговую строку) ...

КонецЦикла; // Конец цикла по должностям

Шаг 3. Формирование строки внутри группы

Во внутреннем цикле мы наполняем нашу "временную" переменную СтрокаСотрудниковТекущейДолжности. Чтобы список сотрудников выглядел красиво (например, "Иванов И.И., Петров П.П."), используем флаг ПервыйСотрудникВГруппе, который позволяет не ставить запятую перед первым сотрудником в списке.


// ... (начало внутреннего цикла) ...
Для Каждого СтрокаСотр Из МассивСотрудниковПоДолжности Цикл
        
    // Если это не первый сотрудник, добавляем разделитель
    Если НЕ ПервыйСотрудникВГруппе Тогда
        СтрокаСотрудниковТекущейДолжности = СтрокаСотрудниковТекущейДолжности + ", ";
    КонецЕсли;
        
    // Формируем представление сотрудника (например, Фамилия И.О.)
    ПредставлениеСотрудника = РаботаСФизлицами.ФамилияИнициалы(СтрокаСотр.ФизическоеЛицо);
        
    // Добавляем сотрудника к строке текущей группы
    СтрокаСотрудниковТекущейДолжности = СтрокаСотрудниковТекущейДолжности + ПредставлениеСотрудника;
        
    // Сбрасываем флаг после первого сотрудника
    ПервыйСотрудникВГруппе = Ложь;
        
КонецЦикла; // Конец цикла по сотрудникам

Шаг 4. Добавление результата группы к итоговому выводу

После того как внутренний цикл по сотрудникам завершился, у нас есть готовая строка со всеми сотрудниками одной должности. Теперь мы можем красиво ее оформить (например, "Должность: список сотрудников") и добавить к нашей общей итоговой переменной ИтоговаяСтрокаВывода.


    // ... (код после завершения внутреннего цикла) ...
    
    // Формируем итоговую строку для вывода после обработки всех сотрудников одной должности
    // Добавляем перенос строки, чтобы каждая группа была с новой строки
    Если НЕ ПустаяСтрока(ИтоговаяСтрокаВывода) Тогда
        ИтоговаяСтрокаВывода = ИтоговаяСтрокаВывода + Символы.ПС;
    КонецЕсли;

    ИтоговаяСтрокаВывода = ИтоговаяСтрокаВывода + Строка(Должность) + ": " + СтрокаСотрудниковТекущейДолжности;

КонецЦикла; // Конец внешнего цикла по должностям

// Теперь в переменной "ИтоговаяСтрокаВывода" будет содержаться сгруппированный список, например:
// Инженер: Иванов И.И., Петров П.П.
// Бухгалтер: Сидорова С.С.

// Передаем готовую строку в макет
ДанныеОбъекта.Вставить("СгруппированныйСписокСотрудников", ИтоговаяСтрокаВывода);

Альтернативный и более производительный подход: использование запросов

Ручной перебор таблиц в циклах — не всегда самый эффективный способ. Для задач группировки и агрегации данных в 1С предназначен мощный инструмент — Запросы. Использование запроса перекладывает основную работу по обработке данных на систему управления базами данных (СУБД), что обычно гораздо быстрее, чем выполнение циклов в коде 1С.

Хотя стандартный язык запросов 1С не имеет простой функции для объединения строк (как, например, GROUP_CONCAT в MySQL), мы можем использовать запрос для получения уже сгруппированных данных, а затем обработать их.

Например, чтобы получить список должностей и количество сотрудников, можно использовать такой запрос:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    КадровыеДанныеСотрудниковСрезПоследних.Должность КАК Должность,
    |    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ КадровыеДанныеСотрудниковСрезПоследних.Сотрудник) КАК КоличествоСотрудников
    |ИЗ
    |    РегистрСведений.КадровыеДанныеСотрудников.СрезПоследних(&ДатаАктуальности, ) КАК КадровыеДанныеСотрудниковСрезПоследних
    |
    |СГРУППИРОВАТЬ ПО
    |    КадровыеДанныеСотрудниковСрезПоследних.Должность";

Запрос.УстановитьПараметр("ДатаАктуальности", ТекущаяДата());

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

Этот подход не решает задачу вывода ФИО в одной строке, но он является основой для более сложных отчетов, особенно при использовании Системы Компоновки Данных (СКД), где подобные группировки являются штатной и очень мощной функциональностью.

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

← На главную