В процессе разработки в среде «1С:Предприятие» программисты часто сталкиваются с необходимостью агрегировать данные в табличных частях документов или обработок. Стандартный метод Свернуть() объекта ТаблицаЗначений отлично справляется с этой задачей, когда нужно объединить абсолютно все дублирующиеся строки. Однако на практике нередко возникает более сложная задача: выполнить сворачивание строк по определенному отбору.
Например, в табличной части документа присутствуют разные материалы. Нам необходимо объединить (просуммировать) строки с «Материалом 2», но при этом строки с «Материалом 1» должны остаться несвернутыми, даже если они дублируются. К сожалению, встроенного метода вроде СвернутьСОтбором() в платформе 1С не существует. Тем не менее, эту задачу можно элегантно решить несколькими способами.
В этой статье мы подробно рассмотрим четыре наиболее эффективных и популярных метода реализации выборочной свертки данных, проанализируем их плюсы и минусы, а также разберем готовые примеры кода.
Этот метод по праву считается одним из самых красивых и быстродействующих решений, когда работа ведется непосредственно с коллекцией в оперативной памяти. Идея заключается в том, чтобы временно добавить в таблицу значений дополнительную колонку, которая будет содержать уникальные маркеры для тех строк, которые не должны сворачиваться.
Давайте разберем алгоритм по шагам:
ВременныйКлюч) числового типа.0. Для всех остальных строк (которые сворачивать нельзя) записываем в эту колонку уникальное число — например, инкрементируемый счетчик.Свернуть(), передавая в качестве полей группировки исходные колонки и нашу новую колонку ВременныйКлюч.0, поэтому они успешно свернутся в одну строку с суммированием ресурсов.Посмотрим на пример практической реализации данного алгоритма:
// Выгружаем данные табличной части "Товары" в таблицу значений
ТаблицаДанных = Объект.Товары.Выгрузить();
// Добавляем временную колонку для уникальных идентификаторов группировки
ТаблицаДанных.Колонки.Add("ВременныйКлюч", Новый ОписаниеТипов("Число"));
УникальныйИндекс = 1;
ИсключаемыйМатериал = Справочники.Номенклатура.НайтиПоНаименованию("Материал 1");
Для Каждого СтрокаТаблицы Из ТаблицаДанных Цикл
Если СтрокаТаблицы.Номенклатура = ИсключаемыйМатериал Тогда
// Строки с "Материал 1" не должны сворачиваться, присваиваем уникальный маркер
СтрокаТаблицы.ВременныйКлюч = УникальныйИндекс;
УникальныйИндекс = УникальныйИндекс + 1;
Иначе
// Строки, подлежащие свертке, получают одинаковый маркер
СтрокаТаблицы.ВременныйКлюч = 0;
КонецЕсли;
КонецЦикла;
// Выполняем сворачивание. Колонка "ВременныйКлюч" добавлена в список группировки
ТаблицаДанных.Свернуть("Номенклатура, ВременныйКлюч", "Количество, Сумма");
// Удаляем ненужную более колонку перед загрузкой в ТЧ
ТаблицаДанных.Колонки.Удалить("ВременныйКлюч");
// Заполняем табличную часть новыми свернутыми данными
Объект.Товары.Загрузить(ТаблицаДанных);
Преимущества метода: Высокая скорость работы на стороне сервера, лаконичный код, сохранение исходной структуры типов данных табличной части.
Если в табличной части документа содержатся сотни или тысячи строк, обработка коллекции в цикле на встроенном языке может снизить производительность системы. В таких сценариях эффективнее переложить задачу на СУБД с помощью механизма запросов. Для освоения тонкостей построения запросов можно использовать методический материал по работе с запросами в 1С.
Мы можем передать таблицу значений в качестве параметра запроса, использовать конструкцию ВЫБОР КОГДА для создания искусственного уникального поля группировки прямо в запросе, выполнить операцию СГРУППИРОВАТЬ ПО и получить готовый результат.
Рассмотрим пример построения такого запроса:
// Выгружаем ТЧ в таблицу значений
ТаблицаДляЗапроса = Объект.Товары.Выгрузить();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВременнаяТЗ.Номенклатура КАК Номенклатура,
| ВременнаяТЗ.Количество КАК Количество,
| ВременнаяТЗ.Сумма КАК Сумма,
| ВременнаяТЗ.НомерСтроки КАК НомерСтроки
|ПОМЕСТИТЬ ВТ_Исходная
|ИЗ
| &ПараметрТаблица КАК ВременнаяТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| СУММА(ВТ.Количество) КАК Количество,
| СУММА(ВТ.Сумма) КАК Сумма
|ИЗ
| ВТ_Исходная КАК ВТ
|
|СГРУППИРОВАТЬ ПО
| ВТ.Номенклатура,
| ВЫБОР
| КОГДА ВТ.Номенклатура = &ИсключаемыйМатериал
| ТОГДА ВТ.НомерСтроки
| ИНАЧЕ 0
| КОНЕЦ";
Запрос.УстановитьПараметр("ПараметрТаблица", ТаблицаДляЗапроса);
Запрос.УстановитьПараметр("ИсключаемыйМатериал", Справочники.Номенклатура.НайтиПоНаименованию("Материал 1"));
// Выполняем запрос и загружаем очищенные данные обратно в ТЧ
Объект.Товары.Загрузить(Запрос.Выполнить().Выгрузить());
Как это работает: Конструкция ВЫБОР КОГДА проверяет каждую строку. Если это «Материал 1» (который нельзя сворачивать), то в поле группировки подставляется уникальный номер строки (НомерСтроки). Если это любой другой материал — подставляется фиксированный 0. В результате группировки СУБД объединит только те строки, у которых совпали и номенклатура, и результат условного выражения.
Этот способ основан на классическом подходе разделения логики. Мы разбиваем исходную таблицу значений на две обособленные части: ту, которую нужно свернуть по определенным правилам, и ту, которую трогать нельзя. Затем проводим стандартную операцию сворачивания над первой таблицей и объединяем результаты.
Рассмотрим алгоритм действий:
Скопировать().Свернуть().Посмотрим, как выглядит программный код для реализации этой логики:
ТЗ_Исходная = Объект.Товары.Выгрузить();
// Задаем отбор для строк, которые МЫ ХОТИМ свернуть (например, Материал 2)
МатериалДляСвертки = Справочники.Номенклатура.НайтиПоНаименованию("Материал 2");
СтруктураОтбора = Новый Структура("Номенклатура", МатериалДляСвертки);
// Шаг 1: Копируем только те строки, которые нужно свернуть
ТЗ_ДляСвертки = ТЗ_Исходная.Скопировать(СтруктураОтбора);
ТЗ_ДляСвертки.Свернуть("Номенклатура", "Количество, Сумма");
// Шаг 2: Удаляем из исходной таблицы строки, подлежащие сворачиванию
// Обход делаем обязательно с конца коллекции!
ВсегоСтрок = ТЗ_Исходная.Количество();
Для Индекс = 1 По ВсегоСтрок Цикл
ОбратныйИндекс = ВсегоСтрок - Индекс;
ТекущаяСтрока = ТЗ_Исходная[ОбратныйИндекс];
Если ТекущаяСтрока.Номенклатура = МатериалДляСвертки Тогда
ТЗ_Исходная.Удалить(ТекущаяСтрока);
КонецЕсли;
КонецЦикла;
// Шаг 3: Добавляем свернутые строки обратно в исходную ТЗ
Для Каждого СтрокаИзСвернутых Из ТЗ_ДляСвертки Цикл
ЗаполнитьЗначенияСвойств(ТЗ_Исходная.Добавить(), СтрокаИзСвернутых);
// Или используйте метод ЗаполнитьЗначенияСвойств() для ускорения процесса
КонецЦикла;
// Загружаем результат обратно в форму
Объект.Товары.Загрузить(ТЗ_Исходная);
Обратите внимание: При использовании этого метода свернутые строки будут добавлены в самый конец табличной части. Если для бизнес-логики важен изначальный порядок следования строк, лучше воспользоваться Способом 1 или Способом 2.
Иногда возникает необходимость выполнить свертку табличной части непосредственно на клиенте (в кодовой секции &НаКлиенте), например, при изменении реквизитов формы пользователем интерактивно, без отправки контекста формы на сервер. Разобраться в тонкостях клиент-серверного взаимодействия поможет руководство по управлению контекстом выполнения кода в управляемых формах.
На клиенте мы работаем с объектом типа ДанныеФормыКоллекция. У этого объекта отсутствуют методы Выгрузить() и Свернуть(). В этой ситуации мы вынуждены использовать ручной обход строк.
Рассмотрим алгоритм ручного поиска дубликатов:
&НаКлиенте
Процедура СвернутьСтрокиНаКлиенте()
Индекс = 0;
Пока Индекс < Объект.Товары.Количество() Цикл
СтрокаТекущая = Объект.Товары[Индекс];
// Сворачиваем только определенный материал (например, "Материал 2")
Если СтрокаТекущая.Номенклатура = СсылкаНаМатериал2 Тогда
// Ищем дубликаты этой номенклатуры в оставшейся части таблицы
ИндексПоиска = Индекс + 1;
Пока ИндексПоиска < Объект.Товары.Количество() Цикл
СтрокаДубликат = Объект.Товары[ИндексПоиска];
Если СтрокаДубликат.Номенклатура = СтрокаТекущая.Номенклатура Тогда
// Суммируем ресурсы в основную строку
СтрокаТекущая.Количество = СтрокаТекущая.Количество + СтрокаДубликат.Количество;
СтрокаТекущая.Сумма = СтрокаТекущая.Сумма + СтрокаДубликат.Сумма;
// Удаляем дубликат, при этом индекс поиска не увеличиваем,
// так как все последующие строки сместились вверх
Объект.Товары.Удалить(СтрокаДубликат);
Иначе
ИндексПоиска = ИндексПоиска + 1;
КонецЕсли;
КонецЦикла;
КонецЕсли;
Индекс = Индекс + 1;
КонецЦикла;
КонецПроцедуры
Важное примечание: Этот метод подходит только для относительно небольших объемов данных (до 50-100 строк). При больших объемах ручной перебор "каждый с каждым" будет работать медленно из-за квадратичной сложности алгоритма. Для тяжелых таблиц всегда используйте серверные методы группировки (через вспомогательную колонку или запросы).
Мы проанализировали четыре различных подхода к реализации свертки строк табличной части с отбором в 1С. Каждый из них применим в своей ситуации: