При работе с объектом ТаблицаЗначений в платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с ограничением штатного метода Свернуть(). Данный метод умеет только группировать строки по заданным колонкам и суммировать числовые ресурсы. Если же в результирующей таблице требуется получить максимальное (или минимальное) значение — например, максимальную дату, последнее заполненное строковое значение или непустую ссылку на элемент справочника при объединении записей, — стандартного функционала становится недостаточно.
Проанализируем задачу на типовом примере: имеется таблица сопоставления данных из двух баз (1С и внешней системы). В одной строке может быть заполнено юрлицо из 1С, во второй — юрлицо из внешней системы при общем идентификаторе id. Требуется объединить строки по id, просуммировать числовые показатели и выбрать заполненные (максимальные) значения строковых или ссылочных реквизитов. Разберем основные способы реализации такой группировки.
Если в конфигурации подключена внешняя компонента 1С++, наиболее удобным и лаконичным решением является объект ИндексированнаяТаблица. Он предоставляет встроенный метод группировки с сохранением подчиненных строк во вложенной таблице потомков тзПотомки.
Разберем по шагам алгоритм с использованием ИндексированнаяТаблица:
ИндексированнаяТаблица.тзПотомки) во временную таблицу значений.Посмотрим на пример программного кода:
ИТЗ = СоздатьОбъект("ИндексированнаяТаблица");
ТЗП = СоздатьОбъект("ТаблицаЗначений");
// Загружаем данные из исходной ТЗ
ИТЗ.Загрузить(ИсходнаяТЗ);
// Выполняем группировку по ключевому полю
ИТЗ.Группировать("ИнКолонкаПоКоторойГруппируем:КолонкаПоКоторойГруппируем", "", 1);
ИТЗ.ВыбратьСтроки();
Пока ИТЗ.ПолучитьСтроку() = 1 Цикл
// Выгружаем строки текущей группы во временную ТЗ
ИТЗ.тзПотомки.Выгрузить(ТЗП);
// Сортируем по убыванию нужного поля для поиска максимума
ТЗП.Сортировать("КолонкаПоКоторойНуженМаксимум-");
// Первая строка содержит максимальное значение
ТЗП.ПолучитьСтрокуПоНомеру(1);
МаксЗначение = ТЗП.КолонкаПоКоторойНуженМаксимум;
// Выполняем дальнейшую обработку или заполнение результирующей таблицы
КонецЦикла;
Если использование внешних библиотек невозможно или требуется чистое решение на встроенном языке 1С 7.7, наиболее эффективным является однопроходный обход предварительно отсортированной таблицы значений. Это позволяет избежать создания сотен промежуточных таблиц и квадратичной сложности алгоритма.
Разберем логику работы этого метода:
ТЗ.Сортировать("ПолеГруппировки+,ПолеМаксимума-");.ТаблицаЗначений с нужной структурой колонок.Рассмотрим реализацию на примере кода:
// Подготавливаем результирующую таблицу
ТЗ_Рез = СоздатьОбъект("ТаблицаЗначений");
ТЗ_Рез.НоваяКолонка("ID", "Число");
ТЗ_Рез.НоваяКолонка("Сумма1С", "Число");
ТЗ_Рез.НоваяКолонка("СуммаДругая", "Число");
ТЗ_Рез.НоваяКолонка("Юл1С", "Строка");
ТЗ_Рез.НоваяКолонка("ЮлДругая", "Строка");
// Сортируем исходную таблицу: по ключу - по возрастанию, по искомым полям - по убыванию
ИсходнаяТЗ.Сортировать("ID+,Юл1С-,ЮлДругая-");
ТекID = "";
ТекСумма1 = 0;
ТекСумма2 = 0;
ТекЮл1 = "";
ТекЮл2 = "";
ЕстьДанные = 0;
ИсходнаяТЗ.ВыбратьСтроки();
Пока ИсходнаяТЗ.ПолучитьСтроку() = 1 Цикл
Если ИсходнаяТЗ.ID <> ТекID Тогда
// Записываем накопленные данные предыдущей группы
Если ЕстьДанные = 1 Тогда
ТЗ_Рез.НоваяСтрока();
ТЗ_Рез.ID = ТекID;
ТЗ_Рез.Сумма1С = ТекСумма1;
ТЗ_Рез.СуммаДругая = ТекСумма2;
ТЗ_Рез.Юл1С = ТекЮл1;
ТЗ_Рез.ЮлДругая = ТекЮл2;
КонецЕсли;
// Инициализируем новую группу
ТекID = ИсходнаяТЗ.ID;
ТекСумма1 = ИсходнаяТЗ.Сумма1С;
ТекСумма2 = ИсходнаяТЗ.СуммаДругаяСистема;
// Так как сортировка была по убыванию, первое значение является максимальным (непустым)
ТекЮл1 = ИсходнаяТЗ.Юл1С;
ТекЮл2 = ИсходнаяТЗ.ЮлДругаяСистема;
ЕстьДанные = 1;
Иначе
// Продолжаем накапливать суммы внутри текущей группы
ТекСумма1 = ТекСумма1 + ИсходнаяТЗ.Сумма1С;
ТекСумма2 = ТекСумма2 + ИсходнаяТЗ.СуммаДругаяСистема;
КонецЕсли;
КонецЦикла;
// Сбрасываем последнюю группу в результат
Если ЕстьДанные = 1 Тогда
ТЗ_Рез.НоваяСтрока();
ТЗ_Рез.ID = ТекID;
ТЗ_Рез.Сумма1С = ТекСумма1;
ТЗ_Рез.СуммаДругая = ТекСумма2;
ТЗ_Рез.Юл1С = ТекЮл1;
ТЗ_Рез.ЮлДругая = ТекЮл2;
КонецЕсли;
Еще один штатный прием заключается в разделении задачи на суммирование и поиск крайних значений с помощью двух таблиц значений:
Свернуть("ПоляГруппировки", "СуммируемыеПоля"). Получаем уникальный список групп с посчитанными суммами.НайтиЗначение() или позиционирование по ключам).Если требуется находить максимум среди объектов ссылочного типа (справочники, документы) или выбирать между пустой и заполненной ссылкой, следует учитывать нюансы сравнения в платформе 1С 7.7:
ПустоеЗначение(). Если сохраненное значение пустое, а текущее заполнено — перезаписываем его.ЗначениеВСтрокуВнутр(). Полученную уникальную строку или идентификатор объекта помещают во вспомогательную колонку, по которой затем выполняется стандартная сортировка или сравнение. Для восстановления объекта обратно применяется функция ЗначениеИзСтрокиВнутр().Таким образом, для простых задач сопоставления достаточно однопроходного алгоритма с предварительной сортировкой (Способ 2), а при наличии компоненты 1С++ наиболее масштабируемым и быстрым решением будет использование ИндексированнаяТаблица или прямых SQL-запросов к таблице в памяти.