Группировка строк таблицы значений: от Свернуть до дерева с итогами

· 9 мин

Содержание

Чтобы сгруппировать таблицу значений в 1С 8.3, вызовите у неё метод Свернуть("КолонкиГруппировки", "КолонкиСуммирования"). Например, ТЗ.Свернуть("Контрагент", "Сумма") оставит по одной строке на контрагента, а суммы сложит. Метода с названием «Сгруппировать» у объекта ТаблицаЗначений нет: СГРУППИРОВАТЬ ПО — это конструкция языка запросов, и её применяют, когда нужны максимум, минимум, среднее или подытоги.

Ниже — рабочие примеры для обоих путей: как подготовить колонки, когда сортировать, как считать итоги и количество строк, как получить сводную таблицу с промежуточными итогами и что делать с большими объёмами.

Подготовка данных: колонки и типы в таблице значений

Группировка ломается чаще всего не на самом методе, а на колонках без типа. Поэтому таблицу лучше собирать с явным описанием типов.

ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Контрагент", Новый ОписаниеТипов("СправочникСсылка.Контрагенты"));
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
ТЗ.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));

Что даёт типизация:

  • ✅ у новой строки числовая колонка сразу равна 0, а не Неопределено, поэтому ручное сложение не падает;
  • ✅ таблицу можно передать параметром в запрос — колонку без типа запрос не примет;
  • ✅ в ячейку не попадёт строка «1 500,00» вместо числа, которую потом не сложить.

Если таблица пришла из Запрос.Выполнить().Выгрузить(), типы у колонок уже есть. Проблемы обычно с таблицами, собранными вручную через Колонки.Добавить("Имя") без второго параметра.

Где выполнять код

Объект ТаблицаЗначений недоступен в тонком клиенте. Реквизит формы с этим типом на клиенте виден как коллекция данных формы, и метода Свернуть у неё нет. Группируйте на сервере:

&НаСервере
Процедура СвернутьТаблицуНаСервере()
    ТЗ = РеквизитФормыВЗначение("Таблица");
    ТЗ.Свернуть("Контрагент", "Сумма");
    ЗначениеВРеквизитФормы(ТЗ, "Таблица");
КонецПроцедуры

Метод Свернуть: как сгруппировать строки таблицы значений

Синтаксис: Свернуть(<КолонкиГруппировки>, <КолонкиСуммирования>). Оба параметра — строки с именами колонок через запятую.

// Одна строка на пару «контрагент + номенклатура», количество и сумма сложены
ТЗ.Свернуть("Контрагент, Номенклатура", "Количество, Сумма");

Как работает метод:

  1. Строки с одинаковыми значениями во всех колонках группировки сливаются в одну.
  2. Значения колонок суммирования складываются.
  3. Колонки, не указанные ни в одном параметре, удаляются из таблицы.
  4. Изменения происходят в самой таблице, новая не создаётся.

Пример на цифрах. Было:

КонтрагентНоменклатураСумма
АльфаБумага1 200
АльфаТонер3 400
БетаБумага800
АльфаБумага600

После ТЗ.Свернуть("Контрагент", "Сумма") останется две строки: Альфа — 5 200 (1 200 + 3 400 + 600), Бета — 800. Колонка «Номенклатура» исчезнет.

Частые варианты вызова

// Только уникальные значения, без сумм
ТЗ.Свернуть("Контрагент");

// Убрать полные дубли строк — перечислить все колонки
ТЗ.Свернуть("Контрагент, Номенклатура, Сумма");

// Сохранить исходную таблицу: сворачиваем копию
Свод = ТЗ.Скопировать();
Свод.Свернуть("Контрагент", "Сумма");

// Свернуть только часть строк — по отбору
Отбор = Новый Структура("Контрагент", Контрагент);
Свод = ТЗ.Скопировать(Отбор, "Номенклатура, Сумма");
Свод.Свернуть("Номенклатура", "Сумма");

Одна и та же колонка не может быть одновременно в группировке и в суммировании — платформа выдаст ошибку. Свернуть безвозвратно удаляет неуказанные колонки: если исходные строки ещё нужны, сворачивайте копию.

Сортировка: нужна ли она перед группировкой

Для Свернуть предварительная сортировка не нужна: метод сам находит одинаковые ключи в любом порядке. Сортировать стоит результат, потому что порядок строк после свёртки не стоит считать гарантированным.

ТЗ.Свернуть("Контрагент", "Сумма");
ТЗ.Сортировать("Сумма Убыв");

Формат строки сортировки: имена колонок через запятую, после имени — Возр или Убыв. Без направления действует Возр.

ТЗ.Сортировать("Контрагент, Дата Убыв");

Когда без сортировки не обойтись

Сортировка обязательна для ручной группировки «по смене ключа». Это классический алгоритм: упорядочили строки, прошли циклом и открываем новую группу, когда значение ключа сменилось. Так делают, если в каждой группе нужно что-то сложнее суммы — например, склеить номера документов в одну строку.

// В исходной таблице есть колонка «Документ»
ТЗ.Сортировать("Контрагент");
Результат = ТЗ.СкопироватьКолонки("Контрагент, Сумма");
Результат.Колонки.Добавить("Документы", Новый ОписаниеТипов("Строка"));

Группа = Неопределено;
Для Каждого СтрокаТЗ Из ТЗ Цикл
    Если Группа = Неопределено Или Группа.Контрагент <> СтрокаТЗ.Контрагент Тогда
        Группа = Результат.Добавить();
        Группа.Контрагент = СтрокаТЗ.Контрагент;
    КонецЕсли;
    Группа.Сумма = Группа.Сумма + СтрокаТЗ.Сумма;
    Группа.Документы = Группа.Документы + ?(ПустаяСтрока(Группа.Документы), "", ", ")
        + Строка(СтрокаТЗ.Документ);
КонецЦикла;

Здесь важен порядок, а не направление: после сортировки равные значения стоят рядом, и каждая группа проходится один раз. СкопироватьКолонки переносит и типы, поэтому Группа.Сумма у новой строки равна 0.

Итоги, количество строк и имена колонок результата

Итог по колонке

Общая сумма колонки считается одной строкой, без свёртки:

ВсегоПоТаблице = ТЗ.Итог("Сумма");

Итог по одной группе — через копию с отбором:

Отбор = Новый Структура("Контрагент", Контрагент);
СуммаПоКонтрагенту = ТЗ.Скопировать(Отбор, "Сумма").Итог("Сумма");

Сколько строк попало в группу

Свернуть умеет только складывать. Чтобы получить число строк, добавьте колонку-счётчик со значением 1 и сложите её:

ТЗ.Колонки.Добавить("ЧислоСтрок", Новый ОписаниеТипов("Число"));
ТЗ.ЗаполнитьЗначения(1, "ЧислоСтрок");
ТЗ.Свернуть("Контрагент", "Сумма, ЧислоСтрок");

Количество разных значений внутри группы (например, сколько разных товаров у контрагента) считается двумя свёртками: сначала Свернуть("Контрагент, Номенклатура") — останутся уникальные пары, затем счётчик и Свернуть("Контрагент", "ЧислоСтрок").

Свои имена итоговых колонок

У Свернуть нет псевдонимов: сумма остаётся в той же колонке под тем же именем. Если в результате колонка должна называться иначе, есть два пути:

  • 📌 заранее завести колонку с нужным именем, скопировать в неё значения и суммировать уже её;
  • 📌 группировать запросом, где имя задаётся через КАК, например СУММА(Т.Сумма) КАК СуммаИтого.

Максимум и минимум без запроса

Если запрос не хочется городить, максимум по группе собирают в Соответствие:

Максимумы = Новый Соответствие;
Для Каждого Строка Из ТЗ Цикл
    Текущее = Максимумы.Получить(Строка.Контрагент);
    Если Текущее = Неопределено Или Строка.Сумма > Текущее Тогда
        Максимумы.Вставить(Строка.Контрагент, Строка.Сумма);
    КонецЕсли;
КонецЦикла;

Группировка таблицы значений запросом: СГРУППИРОВАТЬ ПО и ИТОГИ

Запрос к таблице значений нужен, когда в одной группировке требуются разные агрегаты: сумма, максимум, среднее, количество различных. Таблицу передают параметром, сначала помещают во временную таблицу, а затем группируют её.

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Т.Контрагент КАК Контрагент,
|    Т.Номенклатура КАК Номенклатура,
|    Т.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
|    &ТЗ КАК Т
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|    ВТ.Контрагент КАК Контрагент,
|    СУММА(ВТ.Сумма) КАК СуммаИтого,
|    МАКСИМУМ(ВТ.Сумма) КАК МаксимальнаяСумма,
|    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ.Номенклатура) КАК ЧислоТоваров
|ИЗ
|    ВТ_Данные КАК ВТ
|СГРУППИРОВАТЬ ПО
|    ВТ.Контрагент";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
Свод = Запрос.Выполнить().Выгрузить();

Временная таблица из первого запроса пакета видна второму запросу в том же Выполнить(), отдельный менеджер временных таблиц здесь не нужен. Если ВТ_Данные понадобится в следующих запросах, заведите Новый МенеджерВременныхТаблиц и присвойте его свойству Запрос.МенеджерВременныхТаблиц.

Сводная таблица с промежуточными итогами

Конструкция ИТОГИ добавляет строки подытогов по группировкам. Результат удобно выгрузить в дерево значений: верхний уровень — контрагент с общей суммой, вложенный — товары.

Запрос.Текст =
"ВЫБРАТЬ
|    Т.Контрагент КАК Контрагент,
|    Т.Номенклатура КАК Номенклатура,
|    Т.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_Данные
|ИЗ
|    &ТЗ КАК Т
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|    ВТ.Контрагент КАК Контрагент,
|    ВТ.Номенклатура КАК Номенклатура,
|    СУММА(ВТ.Сумма) КАК Сумма
|ИЗ
|    ВТ_Данные КАК ВТ
|СГРУППИРОВАТЬ ПО
|    ВТ.Контрагент,
|    ВТ.Номенклатура
|ИТОГИ
|    СУММА(Сумма)
|ПО
|    ОБЩИЕ,
|    Контрагент";
Запрос.УстановитьПараметр("ТЗ", ТЗ);
Дерево = Запрос.Выполнить().Выгрузить(ОбходРезультатаЗапроса.ПоГруппировкам);

ОБЩИЕ даёт корневую строку с итогом по всей таблице. Без него верхним уровнем дерева будут сразу контрагенты. Для вывода в отчёт или табличный документ такую выборку часто обходят через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), а не выгружают целиком.

Какой способ выбрать

ЗадачаСпособНа что смотреть
Сумма по группамСвернутьУдаляет неуказанные колонки
Общий итог колонкиИтогТолько числа
Число строк в группеСвернуть + колонка-счётчикЛишняя колонка в результате
Максимум, минимум, среднееЗапрос с СГРУППИРОВАТЬ ПОКолонки должны быть типизированы
Подытоги по уровнямЗапрос с ИТОГИ → дерево значенийРезультат — дерево, а не таблица
Склеить значения в строкуСортировка + цикл по смене ключаКод длиннее, но гибче
Что нужно получить из таблицы значений?

Скорость группировки на больших таблицах

На десятках и сотнях тысяч строк выбор способа уже заметен по времени.

  • ⚡ Свернуть быстрее самописного цикла. Цикл, в котором для каждой строки ищется группа через НайтиСтроки без индекса, перебирает таблицу заново на каждом шаге. Время растёт квадратично.
  • ⚡ Ищете строки по ключу — добавьте индекс. ТЗ.Индексы.Добавить("Контрагент") ускоряет НайтиСтроки и Найти по этой колонке. Индекс заводите один раз до цикла.
  • ⚡ Данные из базы группируйте в исходном запросе. Если таблица получена выгрузкой из регистра, а потом сворачивается, перенесите СГРУППИРОВАТЬ ПО в сам запрос. СУБД вернёт уже готовые группы, и в память не попадут лишние строки.
  • ⚡ Передача таблицы в запрос не бесплатна. Параметр-таблица переносится во временную таблицу, а в клиент-серверном варианте — на сервер СУБД. Для небольших таблиц это незаметно, для очень больших сравните со Свернуть и циклом по Соответствие.
  • ⚡ Сокращайте колонки до свёртки. ТЗ.Скопировать(, "Контрагент, Сумма") перед Свернуть уменьшает объём копируемых данных, если исходная таблица широкая.
  • ⚡ Не таскайте таблицу между клиентом и сервером. Сворачивайте там же, где она собрана, и на форму отдавайте уже свёрнутый результат.

Для проверки разницы на своих данных включите замер производительности в отладчике Конфигуратора и прогоните оба варианта на копии реальной выборки.

Частые ошибки при группировке

  • ❌ Пропала нужная колонка. Её не перечислили ни в группировке, ни в суммировании. Добавьте колонку в один из параметров или сворачивайте копию.
  • ❌ Суммы не сложились, строки «не слились». В колонке группировки визуально одинаковые значения различаются: лишний пробел в строке, разное время в дате, пустая ссылка против Неопределено. Приведите значения к одному виду до свёртки, например даты — через НачалоДня().
  • ❌ Ошибка при передаче таблицы в запрос. Обычно у какой-то колонки не задан тип. Пересоздайте колонку с ОписаниеТипов.
  • ❌ «Метод объекта не обнаружен (Свернуть)». Код выполняется на клиенте с данными формы. Перенесите свёртку в серверную процедуру.
  • ❌ Ручная группировка задваивает группы. Таблицу не отсортировали по ключу перед циклом «по смене ключа», и одинаковые значения оказались не рядом.

Частые вопросы

Можно ли свернуть дерево значений так же, как таблицу?
Нет, у дерева значений метода Свернуть нет. Выгрузите нужный уровень в таблицу значений, сверните её или стройте дерево сразу запросом с ИТОГИ.
Что будет, если в колонке суммирования не числа?
Суммирование рассчитано на числовые колонки. Задайте колонке тип Число, чтобы результат был предсказуемым и не зависел от содержимого ячеек.
Как сгруппировать по части даты, например по месяцу?
Добавьте колонку «Месяц», заполните её через НачалоМесяца(Строка.Дата) и сворачивайте по ней. В запросе то же самое делает функция НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ).
Можно ли сгруппировать без учёта регистра строк?
Свернуть сравнивает строки как есть. Заведите служебную колонку с ВРег(Строка.Наименование), сверните по ней и удалите её после свёртки.
Чем Свернуть отличается от ВыгрузитьКолонку?
ВыгрузитьКолонку возвращает массив всех значений колонки, включая повторы. Для списка уникальных значений сначала выполните Свернуть по этой колонке, а потом выгрузите её.