Как свернуть строки табличной части по отбору в 1С?

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

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

Например, в табличной части документа присутствуют разные материалы. Нам необходимо объединить (просуммировать) строки с «Материалом 2», но при этом строки с «Материалом 1» должны остаться несвернутыми, даже если они дублируются. К сожалению, встроенного метода вроде СвернутьСОтбором() в платформе 1С не существует. Тем не менее, эту задачу можно элегантно решить несколькими способами.

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

Способ 1: Метод вспомогательной колонки (Наиболее изящный)

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

Давайте разберем алгоритм по шагам:

  1. Выгружаем табличную часть документа во временную таблицу значений (ТЗ).
  2. Добавляем в эту ТЗ новую колонку (например, с именем ВременныйКлюч) числового типа.
  3. Обходим таблицу в цикле. Для строк, соответствующих нашему условию свертки (например, «Материал 2»), оставляем значение ключа равным 0. Для всех остальных строк (которые сворачивать нельзя) записываем в эту колонку уникальное число — например, инкрементируемый счетчик.
  4. Вызываем стандартный метод Свернуть(), передавая в качестве полей группировки исходные колонки и нашу новую колонку ВременныйКлюч.
  5. Поскольку у несворачиваемых строк значения во вспомогательной колонке будут уникальными, платформа не станет объединять их между собой. А вот строки с «Материалом 2» имеют одинаковый ключ 0, поэтому они успешно свернутся в одну строку с суммированием ресурсов.
  6. Удаляем вспомогательную колонку и загружаем результат обратно в табличную часть.

Посмотрим на пример практической реализации данного алгоритма:


// Выгружаем данные табличной части "Товары" в таблицу значений
ТаблицаДанных = Объект.Товары.Выгрузить();

// Добавляем временную колонку для уникальных идентификаторов группировки
ТаблицаДанных.Колонки.Add("ВременныйКлюч", Новый ОписаниеТипов("Число"));

УникальныйИндекс = 1;
ИсключаемыйМатериал = Справочники.Номенклатура.НайтиПоНаименованию("Материал 1");

Для Каждого СтрокаТаблицы Из ТаблицаДанных Цикл
    Если СтрокаТаблицы.Номенклатура = ИсключаемыйМатериал Тогда
        // Строки с "Материал 1" не должны сворачиваться, присваиваем уникальный маркер
        СтрокаТаблицы.ВременныйКлюч = УникальныйИндекс;
        УникальныйИндекс = УникальныйИндекс + 1;
    Иначе
        // Строки, подлежащие свертке, получают одинаковый маркер
        СтрокаТаблицы.ВременныйКлюч = 0;
    КонецЕсли;
КонецЦикла;

// Выполняем сворачивание. Колонка "ВременныйКлюч" добавлена в список группировки
ТаблицаДанных.Свернуть("Номенклатура, ВременныйКлюч", "Количество, Сумма");

// Удаляем ненужную более колонку перед загрузкой в ТЧ
ТаблицаДанных.Колонки.Удалить("ВременныйКлюч");

// Заполняем табличную часть новыми свернутыми данными
Объект.Товары.Загрузить(ТаблицаДанных);

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

Способ 2: Запрос с условной группировкой (Рекомендуется для больших таблиц)

Если в табличной части документа содержатся сотни или тысячи строк, обработка коллекции в цикле на встроенном языке может снизить производительность системы. В таких сценариях эффективнее переложить задачу на СУБД с помощью механизма запросов. Для освоения тонкостей построения запросов можно использовать методический материал по работе с запросами в 1С.

Мы можем передать таблицу значений в качестве параметра запроса, использовать конструкцию ВЫБОР КОГДА для создания искусственного уникального поля группировки прямо в запросе, выполнить операцию СГРУППИРОВАТЬ ПО и получить готовый результат.

Рассмотрим пример построения такого запроса:


// Выгружаем ТЧ в таблицу значений
ТаблицаДляЗапроса = Объект.Товары.Выгрузить();

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВременнаяТЗ.Номенклатура КАК Номенклатура,
    |	ВременнаяТЗ.Количество КАК Количество,
    |	ВременнаяТЗ.Сумма КАК Сумма,
    |	ВременнаяТЗ.НомерСтроки КАК НомерСтроки
    |ПОМЕСТИТЬ ВТ_Исходная
    |ИЗ
    |	&ПараметрТаблица КАК ВременнаяТЗ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ.Номенклатура КАК Номенклатура,
    |	СУММА(ВТ.Количество) КАК Количество,
    |	СУММА(ВТ.Сумма) КАК Сумма
    |ИЗ
    |	ВТ_Исходная КАК ВТ
    |
    |СГРУППИРОВАТЬ ПО
    |	ВТ.Номенклатура,
    |	ВЫБОР
    |		КОГДА ВТ.Номенклатура = &ИсключаемыйМатериал
    |			ТОГДА ВТ.НомерСтроки
    |		ИНАЧЕ 0
    |	КОНЕЦ";

Запрос.УстановитьПараметр("ПараметрТаблица", ТаблицаДляЗапроса);
Запрос.УстановитьПараметр("ИсключаемыйМатериал", Справочники.Номенклатура.НайтиПоНаименованию("Материал 1"));

// Выполняем запрос и загружаем очищенные данные обратно в ТЧ
Объект.Товары.Загрузить(Запрос.Выполнить().Выгрузить());

Как это работает: Конструкция ВЫБОР КОГДА проверяет каждую строку. Если это «Материал 1» (который нельзя сворачивать), то в поле группировки подставляется уникальный номер строки (НомерСтроки). Если это любой другой материал — подставляется фиксированный 0. В результате группировки СУБД объединит только те строки, у которых совпали и номенклатура, и результат условного выражения.

Способ 3: Разделение на две таблицы с последующим слиянием

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

Рассмотрим алгоритм действий:

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

Посмотрим, как выглядит программный код для реализации этой логики:


ТЗ_Исходная = Объект.Товары.Выгрузить();

// Задаем отбор для строк, которые МЫ ХОТИМ свернуть (например, Материал 2)
МатериалДляСвертки = Справочники.Номенклатура.НайтиПоНаименованию("Материал 2");
СтруктураОтбора = Новый Структура("Номенклатура", МатериалДляСвертки);

// Шаг 1: Копируем только те строки, которые нужно свернуть
ТЗ_ДляСвертки = ТЗ_Исходная.Скопировать(СтруктураОтбора);
ТЗ_ДляСвертки.Свернуть("Номенклатура", "Количество, Сумма");

// Шаг 2: Удаляем из исходной таблицы строки, подлежащие сворачиванию
// Обход делаем обязательно с конца коллекции!
ВсегоСтрок = ТЗ_Исходная.Количество();
Для Индекс = 1 По ВсегоСтрок Цикл
    ОбратныйИндекс = ВсегоСтрок - Индекс;
    ТекущаяСтрока = ТЗ_Исходная[ОбратныйИндекс];
    
    Если ТекущаяСтрока.Номенклатура = МатериалДляСвертки Тогда
        ТЗ_Исходная.Удалить(ТекущаяСтрока);
    КонецЕсли;
КонецЦикла;

// Шаг 3: Добавляем свернутые строки обратно в исходную ТЗ
Для Каждого СтрокаИзСвернутых Из ТЗ_ДляСвертки Цикл
    ЗаполнитьЗначенияСвойств(ТЗ_Исходная.Добавить(), СтрокаИзСвернутых);
    // Или используйте метод ЗаполнитьЗначенияСвойств() для ускорения процесса
КонецЦикла;

// Загружаем результат обратно в форму
Объект.Товары.Загрузить(ТЗ_Исходная);

Обратите внимание: При использовании этого метода свернутые строки будут добавлены в самый конец табличной части. Если для бизнес-логики важен изначальный порядок следования строк, лучше воспользоваться Способом 1 или Способом 2.

Способ 4: Ручной обход коллекции (Для работы на клиенте)

Иногда возникает необходимость выполнить свертку табличной части непосредственно на клиенте (в кодовой секции &НаКлиенте), например, при изменении реквизитов формы пользователем интерактивно, без отправки контекста формы на сервер. Разобраться в тонкостях клиент-серверного взаимодействия поможет руководство по управлению контекстом выполнения кода в управляемых формах.

На клиенте мы работаем с объектом типа ДанныеФормыКоллекция. У этого объекта отсутствуют методы Выгрузить() и Свернуть(). В этой ситуации мы вынуждены использовать ручной обход строк.

Рассмотрим алгоритм ручного поиска дубликатов:


&НаКлиенте
Процедура СвернутьСтрокиНаКлиенте()

    Индекс = 0;
    Пока Индекс < Объект.Товары.Количество() Цикл
        СтрокаТекущая = Объект.Товары[Индекс];
        
        // Сворачиваем только определенный материал (например, "Материал 2")
        Если СтрокаТекущая.Номенклатура = СсылкаНаМатериал2 Тогда
            
            // Ищем дубликаты этой номенклатуры в оставшейся части таблицы
            ИндексПоиска = Индекс + 1;
            Пока ИндексПоиска < Объект.Товары.Количество() Цикл
                СтрокаДубликат = Объект.Товары[ИндексПоиска];
                
                Если СтрокаДубликат.Номенклатура = СтрокаТекущая.Номенклатура Тогда
                    // Суммируем ресурсы в основную строку
                    СтрокаТекущая.Количество = СтрокаТекущая.Количество + СтрокаДубликат.Количество;
                    СтрокаТекущая.Сумма = СтрокаТекущая.Сумма + СтрокаДубликат.Сумма;
                    
                    // Удаляем дубликат, при этом индекс поиска не увеличиваем,
                    // так как все последующие строки сместились вверх
                    Объект.Товары.Удалить(СтрокаДубликат);
                Иначе
                    ИндексПоиска = ИндексПоиска + 1;
                КонецЕсли;
            КонецЦикла;
            
        КонецЕсли;
        
        Индекс = Индекс + 1;
    КонецЦикла;

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

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

Резюме

Мы проанализировали четыре различных подхода к реализации свертки строк табличной части с отбором в 1С. Каждый из них применим в своей ситуации:

← На главную