Как сгруппировать данные в таблице значений 1С по подразделениям для отправки одного письма на группу?

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

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

Анализ типичной ошибки: почему дублируются письма?

Проанализируем ситуацию, когда код работает, но выдает неверный результат. Если мы запускаем цикл Для каждого по исходной таблице, внутри которого выполняем поиск по подразделению и отправку, программа будет выполнять действие для каждой строки. Например, если в отделе "Закупки" работают два человека, цикл зайдет в первую строку ("Петров"), найдет всех сотрудников "Закупок" (Петрова и Кузнецова) и отправит письмо. Затем цикл перейдет ко второй строке ("Кузнецов"), снова найдет тех же сотрудников и отправит второе, идентичное письмо.

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

Решение №1: Метод свертки и последующего поиска

Это классический и наиболее наглядный способ решения задачи. Разберем его по шагам:

  1. Создадим копию исходной таблицы значений, чтобы не испортить основные данные.
  2. Используем метод Свернуть(), чтобы оставить в копии только уникальные названия подразделений.
  3. Запустим цикл по этой "свернутой" таблице.
  4. Внутри цикла, используя метод НайтиСтроки(), будем получать массив строк из основной таблицы, относящихся к текущему подразделению.

Рассмотрим реализацию этого алгоритма в коде:


// Предположим, ТЗ_Данные — наша исходная таблица с колонками "Сотрудник" и "ТекущееПодразделение"

// 1. Подготавливаем список уникальных подразделений
ТЗ_Подразделения = ТЗ_Данные.Скопировать();
ТЗ_Подразделения.Свернуть("ТекущееПодразделение");

// 2. Обходим уникальные подразделения
Для каждого СтрПодразделение Из ТЗ_Подразделения Цикл
    
    ТекПодразделение = СтрПодразделение.ТекущееПодразделение;
    
    // 3. Формируем структуру отбора для поиска сотрудников этого отдела
    СтруктураОтбора = Новый Структура;
    СтруктураОтбора.Вставить("ТекущееПодразделение", ТекПодразделение);
    
    // 4. Находим все строки, соответствующие данному подразделению
    МассивСтрокСотрудников = ТЗ_Данные.НайтиСтроки(СтруктураОтбора);
    
    // 5. Вызываем процедуру отправки, передавая ей только нужных сотрудников
    ОтправитьПисьмоРуководителю(МассивСтрокСотрудников, ТекПодразделение);
    
КонецЦикла;

Решение №2: Оптимизация с использованием индексов

Если ваша ТаблицаЗначений содержит тысячи строк, метод НайтиСтроки() может замедлить работу системы, так как при каждом вызове он выполняет сканирование таблицы. Чтобы ускорить процесс, проанализируем возможность индексации. Перед началом цикла мы можем добавить индекс по колонке группировки. Это позволит платформе мгновенно находить нужные строки.


// Добавляем индекс перед началом обработки
ТЗ_Данные.Индексы.Добавить("ТекущееПодразделение");

// Далее используем алгоритм из Решения №1
ТЗ_Подразделения = ТЗ_Данные.Скопировать();
ТЗ_Подразделения.Свернуть("ТекущееПодразделение");

Для каждого СтрПодразделение Из ТЗ_Подразделения Цикл
    Отбор = Новый Структура("ТекущееПодразделение", СтрПодразделение.ТекущееПодразделение);
    НайденныеСтроки = ТЗ_Данные.НайтиСтроки(Отбор);
    ОтправитьПисьмоРуководителю(НайденныеСтроки, СтрПодразделение.ТекущееПодразделение);
КонецЦикла;

Решение №3: Алгоритм одного прохода по отсортированной таблице

Существует более эффективный с точки зрения потребления памяти способ, который не требует создания копии таблицы и использования метода Свернуть(). Мы можем отсортировать исходную таблицу и обойти ее одним циклом, отслеживая смену подразделения. Рассмотрим этот подход:


// 1. Сортируем таблицу, чтобы сотрудники одного отдела шли друг за другом
ТЗ_Данные.Сортировать("ТекущееПодразделение");

МассивДляПисьма = Новый Массив;
ПредПодразделение = Неопределено;

Для каждого СтрокаТЗ Из ТЗ_Данные Цикл
    
    // Если подразделение сменилось (и это не первая итерация), отправляем накопленные данные
    Если ПредПодразделение <> Неопределено И СтрокаТЗ.ТекущееПодразделение <> ПредПодразделение Тогда
        ОтправитьПисьмоРуководителю(МассивДляПисьма, ПредПодразделение);
        МассивДляПисьма.Очистить();
    КонецЕсли;
    
    // Накапливаем сотрудников текущего подразделения
    МассивДляПисьма.Добавить(СтрокаТЗ);
    ПредПодразделение = СтрокаТЗ.ТекущееПодразделение;
    
КонецЦикла;

// Не забываем отправить данные по последнему подразделению после завершения цикла
Если МассивДляПисьма.Количество() > 0 Тогда
    ОтправитьПисьмоРуководителю(МассивДляПисьма, ПредПодразделение);
КонецЕсли;

Решение №4: Использование объекта "Соответствие" (Map)

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

  1. Создаем объект Новый Соответствие.
  2. Обходим основную таблицу один раз.
  3. Для каждой строки проверяем, есть ли подразделение в соответствии. Если нет — создаем новый массив. Добавляем строку в массив.
  4. Вторым циклом обходим само Соответствие и отправляем письма.

Группировка = Новый Соответствие;

Для каждого Строка Из ТЗ_Данные Цикл
    СписокСотрудников = Группировка.Получить(Строка.ТекущееПодразделение);
    Если СписокСотрудников = Неопределено Тогда
        СписокСотрудников = Новый Массив;
        Группировка.Вставить(Строка.ТекущееПодразделение, СписокСотрудников);
    КонецЕсли;
    СписокСотрудников.Добавить(Строка);
КонецЦикла;

// Теперь отправляем письма
Для каждого ЭлементГруппировки Из Группировка Цикл
    // Ключ - подразделение, Значение - массив строк
    ОтправитьПисьмоРуководителю(ЭлементГруппировки.Значение, ЭлементГруппировки.Ключ);
КонецЦикла;

Нюансы работы с методом НайтиСтроки()

Проанализируем важный технический момент, который часто упускают начинающие разработчики. Метод НайтиСтроки() возвращает массив ссылок на объекты типа СтрокаТаблицыЗначений. Это означает следующее:

Использование запросов для группировки ТЗ

Если задача подразумевает сложную логику (например, фильтрацию сотрудников по дате приема или расчет итогов), лучше всего использовать Запрос. Мы можем передать ТаблицуЗначений в запрос как параметр. Это считается "золотым стандартом" профессиональной разработки в 1С.

Рассмотрим краткий пример структуры такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ВТ_Данные.ТекущееПодразделение КАК Подразделение,
    |   ВТ_Данные.Сотрудник КАК Сотрудник
    |ПОМЕСТИТЬ ВТ_Сотрудники
    |ИЗ
    |   &ТЗ_Сотрудники КАК ВТ_Данные
    |;
    |
    |ВЫБРАТЬ
    |   ВТ_Сотрудники.Подразделение КАК Подразделение,
    |   ВТ_Сотрудники.Сотрудник КАК Сотрудник
    |ИЗ
    |   ВТ_Сотрудники КАК ВТ_Сотрудники
    |ИТОГИ ПО
    |   Подразделение";

Запрос.УстановитьПараметр("ТЗ_Сотрудники", ТЗ_Данные);
РезультатЗапроса = Запрос.Выполнить();

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

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

Подводя итог, выбор метода зависит от объема данных и сложности структуры. Для простых задач идеально подходит Решение №1 (Свернуть + НайтиСтроки), а для высоконагруженных систем или сложных отчетов — Решение с Запросом или Алгоритм одного прохода.

← На главную