Группировка строк таблицы значений: от Свернуть до дерева с итогами
Содержание
Чтобы сгруппировать таблицу значений в 1С 8.3, вызовите у неё метод Свернуть("КолонкиГруппировки", "КолонкиСуммирования"). Например, ТЗ.Свернуть("Контрагент", "Сумма") оставит по одной строке на контрагента, а суммы сложит. Метода с названием «Сгруппировать» у объекта ТаблицаЗначений нет: СГРУППИРОВАТЬ ПО — это конструкция языка запросов, и её применяют, когда нужны максимум, минимум, среднее или подытоги.
Ниже — рабочие примеры для обоих путей: как подготовить колонки, когда сортировать, как считать итоги и количество строк, как получить сводную таблицу с промежуточными итогами и что делать с большими объёмами.
Подготовка данных: колонки и типы в таблице значений
Группировка ломается чаще всего не на самом методе, а на колонках без типа. Поэтому таблицу лучше собирать с явным описанием типов.
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Контрагент", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
Что даёт типизация:
- ✅ у новой строки числовая колонка сразу равна 0, а не
Неопределено, поэтому ручное сложение не падает; - ✅ таблицу можно передать параметром в запрос — колонку без типа запрос не примет;
- ✅ в ячейку не попадёт строка «1 500,00» вместо числа, которую потом не сложить.
Если таблица пришла из Запрос.Выполнить().Выгрузить(), типы у колонок уже есть. Проблемы обычно с таблицами, собранными вручную через Колонки.Добавить("Имя") без второго параметра.
Где выполнять код
Объект ТаблицаЗначений недоступен в тонком клиенте. Реквизит формы с этим типом на клиенте виден как коллекция данных формы, и метода Свернуть у неё нет. Группируйте на сервере:
&НаСервере
Процедура СвернутьТаблицуНаСервере()
ТЗ = РеквизитФормыВЗначение("Таблица");
ТЗ.Свернуть("Контрагент", "Сумма");
ЗначениеВРеквизитФормы(ТЗ, "Таблица");
КонецПроцедуры
Метод Свернуть: как сгруппировать строки таблицы значений
Синтаксис: Свернуть(<КолонкиГруппировки>, <КолонкиСуммирования>). Оба параметра — строки с именами колонок через запятую.
// Одна строка на пару «контрагент + номенклатура», количество и сумма сложены
ТЗ.Свернуть("Контрагент, Номенклатура", "Количество, Сумма");
Как работает метод:
- Строки с одинаковыми значениями во всех колонках группировки сливаются в одну.
- Значения колонок суммирования складываются.
- Колонки, не указанные ни в одном параметре, удаляются из таблицы.
- Изменения происходят в самой таблице, новая не создаётся.
Пример на цифрах. Было:
| Контрагент | Номенклатура | Сумма |
|---|---|---|
| Альфа | Бумага | 1 200 |
| Альфа | Тонер | 3 400 |
| Бета | Бумага | 800 |
| Альфа | Бумага | 600 |
После ТЗ.Свернуть("Контрагент", "Сумма") останется две строки: Альфа — 5 200 (1 200 + 3 400 + 600), Бета — 800. Колонка «Номенклатура» исчезнет.
Частые варианты вызова
// Только уникальные значения, без сумм
ТЗ.Свернуть("Контрагент");
// Убрать полные дубли строк — перечислить все колонки
ТЗ.Свернуть("Контрагент, Номенклатура, Сумма");
// Сохранить исходную таблицу: сворачиваем копию
Свод = ТЗ.Скопировать();
Свод.Свернуть("Контрагент", "Сумма");
// Свернуть только часть строк — по отбору
Отбор = Новый Структура("Контрагент", Контрагент);
Свод = ТЗ.Скопировать(Отбор, "Номенклатура, Сумма");
Свод.Свернуть("Номенклатура", "Сумма");
Одна и та же колонка не может быть одновременно в группировке и в суммировании — платформа выдаст ошибку. Свернуть безвозвратно удаляет неуказанные колонки: если исходные строки ещё нужны, сворачивайте копию.
Сортировка: нужна ли она перед группировкой
Для Свернуть предварительная сортировка не нужна: метод сам находит одинаковые ключи в любом порядке. Сортировать стоит результат, потому что порядок строк после свёртки не стоит считать гарантированным.
ТЗ.Свернуть("Контрагент", "Сумма");
ТЗ.Сортировать("Сумма Убыв");
Формат строки сортировки: имена колонок через запятую, после имени — Возр или Убыв. Без направления действует Возр.
ТЗ.Сортировать("Контрагент, Дата Убыв");
Когда без сортировки не обойтись
Сортировка обязательна для ручной группировки «по смене ключа». Это классический алгоритм: упорядочили строки, прошли циклом и открываем новую группу, когда значение ключа сменилось. Так делают, если в каждой группе нужно что-то сложнее суммы — например, склеить номера документов в одну строку.
// В исходной таблице есть колонка «Документ»
ТЗ.Сортировать("Контрагент");
Результат = ТЗ.СкопироватьКолонки("Контрагент, Сумма");
Результат.Колонки.Добавить("Документы", Новый ОписаниеТипов("Строка"));
Группа = Неопределено;
Для Каждого СтрокаТЗ Из ТЗ Цикл
Если Группа = Неопределено Или Группа.Контрагент <> СтрокаТЗ.Контрагент Тогда
Группа = Результат.Добавить();
Группа.Контрагент = СтрокаТЗ.Контрагент;
КонецЕсли;
Группа.Сумма = Группа.Сумма + СтрокаТЗ.Сумма;
Группа.Документы = Группа.Документы + ?(ПустаяСтрока(Группа.Документы), "", ", ")
+ Строка(СтрокаТЗ.Документ);
КонецЦикла;
Здесь важен порядок, а не направление: после сортировки равные значения стоят рядом, и каждая группа проходится один раз. СкопироватьКолонки переносит и типы, поэтому Группа.Сумма у новой строки равна 0.
Итоги, количество строк и имена колонок результата
Итог по колонке
Общая сумма колонки считается одной строкой, без свёртки:
ВсегоПоТаблице = ТЗ.Итог("Сумма");
Итог по одной группе — через копию с отбором:
Отбор = Новый Структура("Контрагент", Контрагент);
СуммаПоКонтрагенту = ТЗ.Скопировать(Отбор, "Сумма").Итог("Сумма");
Сколько строк попало в группу
Свернуть умеет только складывать. Чтобы получить число строк, добавьте колонку-счётчик со значением 1 и сложите её:
ТЗ.Колонки.Добавить("ЧислоСтрок", Новый ОписаниеТипов("Число"));
ТЗ.ЗаполнитьЗначения(1, "ЧислоСтрок");
ТЗ.Свернуть("Контрагент", "Сумма, ЧислоСтрок");
Количество разных значений внутри группы (например, сколько разных товаров у контрагента) считается двумя свёртками: сначала Свернуть("Контрагент, Номенклатура") — останутся уникальные пары, затем счётчик и Свернуть("Контрагент", "ЧислоСтрок").
Свои имена итоговых колонок
У Свернуть нет псевдонимов: сумма остаётся в той же колонке под тем же именем. Если в результате колонка должна называться иначе, есть два пути:
- 📌 заранее завести колонку с нужным именем, скопировать в неё значения и суммировать уже её;
- 📌 группировать запросом, где имя задаётся через
КАК, напримерСУММА(Т.Сумма) КАК СуммаИтого.
Максимум и минимум без запроса
Если запрос не хочется городить, максимум по группе собирают в Соответствие:
Максимумы = Новый Соответствие;
Для Каждого Строка Из ТЗ Цикл
Текущее = Максимумы.Получить(Строка.Контрагент);
Если Текущее = Неопределено Или Строка.Сумма > Текущее Тогда
Максимумы.Вставить(Строка.Контрагент, Строка.Сумма);
КонецЕсли;
КонецЦикла;
Группировка таблицы значений запросом: СГРУППИРОВАТЬ ПО и ИТОГИ
Запрос к таблице значений нужен, когда в одной группировке требуются разные агрегаты: сумма, максимум, среднее, количество различных. Таблицу передают параметром, сначала помещают во временную таблицу, а затем группируют её.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Контрагент КАК Контрагент,
| Т.Номенклатура КАК Номенклатура,
| Т.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТЗ КАК Т
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Контрагент КАК Контрагент,
| СУММА(ВТ.Сумма) КАК СуммаИтого,
| МАКСИМУМ(ВТ.Сумма) КАК МаксимальнаяСумма,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ.Номенклатура) КАК ЧислоТоваров
|ИЗ
| ВТ_Данные КАК ВТ
|СГРУППИРОВАТЬ ПО
| ВТ.Контрагент";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
Свод = Запрос.Выполнить().Выгрузить();
Временная таблица из первого запроса пакета видна второму запросу в том же Выполнить(), отдельный менеджер временных таблиц здесь не нужен. Если ВТ_Данные понадобится в следующих запросах, заведите Новый МенеджерВременныхТаблиц и присвойте его свойству Запрос.МенеджерВременныхТаблиц.
Сводная таблица с промежуточными итогами
Конструкция ИТОГИ добавляет строки подытогов по группировкам. Результат удобно выгрузить в дерево значений: верхний уровень — контрагент с общей суммой, вложенный — товары.
Запрос.Текст =
"ВЫБРАТЬ
| Т.Контрагент КАК Контрагент,
| Т.Номенклатура КАК Номенклатура,
| Т.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
| &ТЗ КАК Т
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Контрагент КАК Контрагент,
| ВТ.Номенклатура КАК Номенклатура,
| СУММА(ВТ.Сумма) КАК Сумма
|ИЗ
| ВТ_Данные КАК ВТ
|СГРУППИРОВАТЬ ПО
| ВТ.Контрагент,
| ВТ.Номенклатура
|ИТОГИ
| СУММА(Сумма)
|ПО
| ОБЩИЕ,
| Контрагент";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
Дерево = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);
ОБЩИЕ даёт корневую строку с итогом по всей таблице. Без него верхним уровнем дерева будут сразу контрагенты. Для вывода в отчёт или табличный документ такую выборку часто обходят через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), а не выгружают целиком.
Какой способ выбрать
| Задача | Способ | На что смотреть |
|---|---|---|
| Сумма по группам | Свернуть | Удаляет неуказанные колонки |
| Общий итог колонки | Итог | Только числа |
| Число строк в группе | Свернуть + колонка-счётчик | Лишняя колонка в результате |
| Максимум, минимум, среднее | Запрос с СГРУППИРОВАТЬ ПО | Колонки должны быть типизированы |
| Подытоги по уровням | Запрос с ИТОГИ → дерево значений | Результат — дерево, а не таблица |
| Склеить значения в строку | Сортировка + цикл по смене ключа | Код длиннее, но гибче |
Скорость группировки на больших таблицах
На десятках и сотнях тысяч строк выбор способа уже заметен по времени.
- ⚡ Свернуть быстрее самописного цикла. Цикл, в котором для каждой строки ищется группа через
НайтиСтрокибез индекса, перебирает таблицу заново на каждом шаге. Время растёт квадратично. - ⚡ Ищете строки по ключу — добавьте индекс.
ТЗ.Индексы.Добавить("Контрагент")ускоряетНайтиСтрокииНайтипо этой колонке. Индекс заводите один раз до цикла. - ⚡ Данные из базы группируйте в исходном запросе. Если таблица получена выгрузкой из регистра, а потом сворачивается, перенесите
СГРУППИРОВАТЬ ПОв сам запрос. СУБД вернёт уже готовые группы, и в память не попадут лишние строки. - ⚡ Передача таблицы в запрос не бесплатна. Параметр-таблица переносится во временную таблицу, а в клиент-серверном варианте — на сервер СУБД. Для небольших таблиц это незаметно, для очень больших сравните со
Свернутьи циклом поСоответствие. - ⚡ Сокращайте колонки до свёртки.
ТЗ.Скопировать(, "Контрагент, Сумма")передСвернутьуменьшает объём копируемых данных, если исходная таблица широкая. - ⚡ Не таскайте таблицу между клиентом и сервером. Сворачивайте там же, где она собрана, и на форму отдавайте уже свёрнутый результат.
Для проверки разницы на своих данных включите замер производительности в отладчике Конфигуратора и прогоните оба варианта на копии реальной выборки.
Частые ошибки при группировке
- ❌ Пропала нужная колонка. Её не перечислили ни в группировке, ни в суммировании. Добавьте колонку в один из параметров или сворачивайте копию.
- ❌ Суммы не сложились, строки «не слились». В колонке группировки визуально одинаковые значения различаются: лишний пробел в строке, разное время в дате, пустая ссылка против
Неопределено. Приведите значения к одному виду до свёртки, например даты — черезНачалоДня(). - ❌ Ошибка при передаче таблицы в запрос. Обычно у какой-то колонки не задан тип. Пересоздайте колонку с
ОписаниеТипов. - ❌ «Метод объекта не обнаружен (Свернуть)». Код выполняется на клиенте с данными формы. Перенесите свёртку в серверную процедуру.
- ❌ Ручная группировка задваивает группы. Таблицу не отсортировали по ключу перед циклом «по смене ключа», и одинаковые значения оказались не рядом.