При обработке данных в 1С часто возникает задача: в объекте ТаблицаЗначений присутствуют строки с совпадающими ключевыми полями (например, Период и Регистратор), но с разными числовыми показателями (колонка Сумма). Требуется проанализировать коллекцию и удалить записи с меньшими суммами, оставив для каждой уникальной пары периода и регистратора только одну строку с максимальным значением.
Рассмотрим несколько проверенных способов решения этой задачи: от алгоритмической сортировки и обратного перебора до обработки данных с помощью механизма запросов.
Разберем наиболее производительный и простой программный метод без использования запросов. Идея заключается в том, чтобы отсортировать коллекцию так, чтобы для каждой группы одинаковых ключевых полей строка с наибольшей суммой всегда находилась первой.
Для этого отсортируем таблицу по возрастанию ключевых полей и по убыванию суммы: Период Возр, Регистратор Возр, Сумма Убыв. При таком подходе первая встреченная строка группы всегда будет содержать максимум, а все последующие строки с теми же значениями Период и Регистратор являются лишними и подлежат удалению.
Чтобы не сбивать индексы при модификации таблицы и не выделять дополнительную память под промежуточные массивы, обход таблицы значений выполним в обратном порядке — с конца в начало с шагом -1.
Посмотрим на пример реализации данного алгоритма:
// Сортируем: максимальные суммы окажутся выше внутри каждой группы
ТЗ.Сортировать("Период Возр, Регистратор Возр, Сумма Убыв");
// Выполняем обход с конца к началу
Для Индекс = ТЗ.Количество() - 1 По 1 Шаг -1 Цикл
ТекущаяСтрока = ТЗ[Индекс];
ПредыдущаяСтрока = ТЗ[Индекс - 1];
// Если ключевые поля совпадают, значит текущая строка имеет меньшую или равную сумму
Если ТекущаяСтрока.Период = ПредыдущаяСтрока.Период
И ТекущаяСтрока.Регистратор = ПредыдущаяСтрока.Регистратор Тогда
ТЗ.Удалить(Индекс);
КонецЕсли;
КонецЦикла;
Проанализируем преимущества этого подхода:
Если требуется обработать большой объем данных на стороне сервера или исходные данные уже формируются запросом, целесообразно переложить фильтрацию на СУБД. В запросе можно сгруппировать записи или воспользоваться оконными функциями.
Важное ограничение платформы: передать коллекцию ТаблицаЗначений в Запрос через менеджер временных таблиц можно только тогда, когда для всех колонок таблицы жестко задано описание типов (объект ОписаниеТипов). Если колонки были добавлены без указания типов, платформа сгенерирует ошибку.
Рассмотрим вариант реализации на платформе 1С:Предприятие 8.3.20 и новее с использованием оконной функции ROW_NUMBER(). Оконная функция позволяет присвоить порядковый номер строкам внутри каждой группы полей Период и Регистратор, упорядочив их по убыванию суммы, после чего отобрать строки с номером 1:
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| ИсходнаяТЗ.Период КАК Период,
| ИсходнаяТЗ.Регистратор КАК Регистратор,
| ИсходнаяТЗ.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &ТаблицаДанных КАК ИсходнаяТЗ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Период КАК Период,
| ВТ.Регистратор КАК Регистратор,
| ВТ.Сумма КАК Сумма
|ИЗ
| (ВЫБРАТЬ
| Данные.Период КАК Период,
| Данные.Регистратор КАК Регистратор,
| Данные.Сумма КАК Сумма,
| ROW_NUMBER() OVER (
| PARTITION BY Данные.Период, Данные.Регистратор
| ORDER BY Данные.Сумма УБЫВ
| ) КАК НомерСтрокиВГруппе
| ИЗ
| ВТ_ИсходныеДанные КАК Данные) КАК ВТ
|ГДЕ
| ВТ.НомерСтрокиВГруппе = 1";
Запрос.УстановитьПараметр("ТаблицаДанных", ТЗ);
ТЗ = Запрос.Выполнить().Выгрузить();
Оконная функция гарантирует, что даже при наличии дополнительных колонок в таблице значений все реквизиты строки с максимальной суммой сохранятся без необходимости делать повторное соединение с группировкой.
В обсуждениях часто встречается рекомендация применить метод НайтиСтроки(). Выясним причину, почему стандартное применение этого метода может привести к сильному замедлению работы алгоритма.
Если вызывать ТЗ.НайтиСтроки() в цикле без предварительной подготовки, платформа каждый раз будет производить полное сканирование таблицы, что даст квадратичную сложность O(N²). Однако если добавить индекс для ключевых колонок через свойство ТЗ.Индексы.Добавить("Период, Регистратор"), поиск начинает работать значительно быстрее.
Тем не менее метод с сортировкой и обратным обходом (Способ 1) остается более лаконичным, надежным и не требует создания промежуточных копий для свертки или группировки ключевых полей.