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