В процессе разработки на платформе 1С программисты часто сталкиваются с задачей группировки данных в объекте ТаблицаЗначений. Стандартный метод Свернуть() отлично справляется с числовыми показателями, суммируя их, но совершенно не подходит для строковых данных. Если нам необходимо не просто схлопнуть строки, а, например, перечислить все договоры контрагента через запятую в одной строке, встроенный метод нам не поможет. Рассмотрим подробнее, как реализовать этот функционал вручную, используя различные подходы в зависимости от версии платформы и требований к производительности.
Метод Свернуть(КолонкиГруппировки, КолонкиСуммирования) работает по простому принципу: строки с одинаковыми значениями в «колонках группировки» объединяются, а значения в «колонках суммирования» складываются. Если в колонку суммирования передать строковое поле, платформа попытается привести его к числу, что зачастую приводит к обнулению данных или некорректным результатам. Наша задача — реализовать алгоритм, который позволит суммировать числа и объединять строки (делать конкатенацию).
Этот метод считается одним из самых понятных и логичных. Мы создаем копию структуры таблицы, сворачиваем в ней только числовые показатели, а затем «добираем» строковые данные из исходной таблицы. Разберем по шагам данный алгоритм:
Посмотрим на пример кода:
// Предположим, ТаблицаИсходная имеет колонки: Контрагент, Итог, Договор
ТаблицаРезультата = ТаблицаИсходная.Скопировать();
// Сворачиваем только по ключевому полю и числам
ТаблицаРезультата.Свернуть("Контрагент", "Итог");
// Для ускорения работы обязательно добавляем индекс по ключевому полю в исходной таблице
ТаблицаИсходная.Индексы.Добавить("Контрагент");
Для Каждого СтрокаРезультата Из ТаблицаРезультата Цикл
МассивНайденныхСтрок = ТаблицаИсходная.НайтиСтроки(Новый Структура("Контрагент", СтрокаРезультата.Контрагент));
Для Каждого СтрокаИсходная Из МассивНайденныхСтрок Цикл
// Проверяем, не пустое ли поле, чтобы красиво расставить запятые
Разделитель = ?(ЗначениеЗаполнено(СтрокаРезультата.Договор), ", ", "");
СтрокаРезультата.Договор = СтрокаРезультата.Договор + Разделитель + СтрокаИсходная.Договор;
КонецЦикла;
КонецЦикла;
Проанализируем ситуацию с производительностью: использование ТаблицаИсходная.Индексы.Добавить() критически важно. Без индекса поиск методом НайтиСтроки на больших таблицах будет работать крайне медленно, так как системе придется каждый раз перебирать всю исходную таблицу целиком.
Если мы хотим избежать создания второй таблицы и работать «на месте», можно воспользоваться алгоритмом сравнения текущей строки с предыдущей. Это более экономный метод с точки зрения памяти. Выясним причину его эффективности: мы проходим по таблице всего один раз.
Алгоритм действий следующий:
Пример реализации с использованием массива для удаления строк:
Таблица.Сортировать("Контрагент");
МассивСтрокДляУдаления = Новый Массив;
ТекущийКонтрагент = Неопределено;
СтрокаДляНакопления = Неопределено;
Для Каждого СтрокаТЗ Из Таблица Цикл
Если СтрокаТЗ.Контрагент <> ТекущийКонтрагент Тогда
// Нашли нового контрагента, фиксируем строку как базовую
ТекущийКонтрагент = СтрокаТЗ.Контрагент;
СтрокаДляНакопления = СтрокаТЗ;
Иначе
// Контрагент тот же, объединяем данные
СтрокаДляНакопления.Итог = СтрокаДляНакопления.Итог + СтрокаТЗ.Итог;
Разделитель = ?(ЗначениеЗаполнено(СтрокаДляНакопления.Договор), ", ", "");
СтрокаДляНакопления.Договор = СтрокаДляНакопления.Договор + Разделитель + СтрокаТЗ.Договор;
// Добавляем лишнюю строку в массив для последующего удаления
МассивСтрокДляУдаления.Добавить(СтрокаТЗ);
КонецЕсли;
КонецЦикла;
// Удаляем ненужные строки
Для Каждого СтрокаДляУдаления Из МассивСтрокДляУдаления Цикл
Таблица.Удалить(СтрокаДляУдаления);
КонецЦикла;
В современных версиях 1С появилась замечательная функция СтрСоединить(), которая работает гораздо быстрее, чем ручная конкатенация через оператор «+». Она позволяет объединить элементы массива в одну строку с заданным разделителем. Проанализируем этот подход, совместив его с методом накопления в массив.
// Сначала получаем уникальный список контрагентов со свернутыми суммами
ТаблицаРезультат = ТаблицаИсходная.Скопировать();
ТаблицаРезультат.Свернуть("Контрагент", "Итог");
ТаблицаРезультат.Колонки.Добавить("ДоговорыСписком");
ТаблицаИсходная.Индексы.Добавить("Контрагент");
Для Каждого СтрокаРез Из ТаблицаРезультат Цикл
МассивДоговоров = Новый Массив;
НайденныеСтроки = ТаблицаИсходная.НайтиСтроки(Новый Структура("Контрагент", СтрокаРез.Контрагент));
Для Каждого СтрИсходная Из НайденныеСтроки Цикл
// Собираем все договоры в массив
Если ЗначениеЗаполнено(СтрИсходная.Договор) Тогда
МассивДоговоров.Добавить(Строка(СтрИсходная.Договор));
КонецЕсли;
КонецЦикла;
// Эффективно объединяем массив в строку
СтрокаРез.ДоговорыСписком = СтрСоединить(МассивДоговоров, ", ");
КонецЦикла;
Многие разработчики предпочитают использовать Запрос для обработки данных. Однако стоит помнить, что в языке запросов 1С нет функции аналогичной GROUP_CONCAT из SQL. Поэтому рассмотрим комбинированный метод:
Это решение будет самым «красивым» с точки зрения архитектуры 1С, так как разделяет ответственность: СУБД (или механизм запросов) считает суммы, а программный код занимается обработкой строк.
При выборе метода обратите внимание на следующие нюансы:
Договор имеет тип ссылки, обязательно используйте Строка(Значение) или Значение.Наименование перед конкатенацией.НайтиСтроки без индексов может «повесить» сеанс. Метод с Соответствие (Map) часто показывает лучшие результаты по скорости, чем НайтиСтроки.ОбщегоНазначения. Там часто встречаются функции для работы с таблицами, которые могут упростить ваш код.Таким образом, мы разобрали основные способы «ручной» свертки таблицы значений. Выбор конкретного варианта зависит от версии вашей платформы и личных предпочтений в стиле кодирования. Наиболее универсальным и производительным для современной 1С 8.3 является сочетание предварительной свертки сумм и последующего заполнения строковых полей через СтрСоединить.