В процессе разработки в среде «1С:Предприятие» программисты часто сталкиваются с задачей агрегации данных. Типичный сценарий: имеется таблица значений, в которой числовые показатели разнесены по двум разным колонкам (например, приход и расход, или положительные и отрицательные разницы). Нам необходимо свернуть эту таблицу по ключевым полям (например, по номенклатуре или складу), но при этом объединить эти две колонки в одну результирующую, рассчитав итоговое сальдо (плюс минус минус).
Стандартный метод таблицы значений Свернуть() позволяет суммировать только значения внутри одной и той же колонки. Напрямую сложить или вычесть колонки друг из друга в момент свертки этот метод не умеет. В данной статье мы подробно разберем три основных способа решения этой задачи: классический обход в цикле с добавлением вспомогательной колонки, использование механизма запросов и применение Системы компоновки данных (СКД). Мы проанализируем особенности каждого подхода, оценим их производительность и выявим подводные камни.
Этот способ является наиболее очевидным и простым в реализации. Суть его заключается в том, что мы сначала подготавливаем таблицу значений: добавляем в нее новую колонку для итоговой суммы, рассчитываем ее значение для каждой строки в цикле (вычитая или складывая исходные колонки), а затем вызываем стандартный метод Свернуть().
Рассмотрим по шагам, как правильно реализовать этот алгоритм, избежав типичных ошибок производительности.
Важнейший нюанс: обязательная типизация новой колонки.
При добавлении вспомогательной колонки через метод Колонки.Добавить() крайне важно явно передавать третьим параметром объект ОписаниеТипов. Если добавить колонку без указания типа, платформа будет выделять память динамически под каждую строку, присваивая ей тип по умолчанию Неопределено. Это критически снижает общую производительность при последующем обходе строк и вызове метода Свернуть() на таблицах, содержащих более нескольких тысяч строк.
Автоматическое удаление исходных колонок:
При вызове метода ТЗ.Свернуть("КолонкиГруппировки", "НоваяОбщаяКолонка") нет необходимости вручную удалять старые колонки (например, «СуммаПлюс» и «СуммаМинус»). Метод Свернуть() автоматически удаляет из таблицы значений все колонки, которые не были перечислены в параметрах группировки или суммирования. Это делает код чище и избавляет от лишних операций.
Проанализируем пример правильного кода:
// 1. Создаем описание типа "Число" для новой колонки
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("Число"));
КвалификаторыЧ = Новый КвалификаторыЧисла(15, 2);
ОписаниеЧисла = Новый ОписаниеТипов(МассивТипов, , , КвалификаторыЧ);
// 2. Добавляем строго типизированную колонку в исходную таблицу значений
ТаблицаДанных.Колонки.Добавить("СуммаИтого", ОписаниеЧисла);
// 3. Заполняем новую колонку в цикле, вычисляя разницу
Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
СтрокаДанных.СуммаИтого = СтрокаДанных.СуммаПлюс - СтрокаДанных.СуммаМинус;
КонецЦикла;
// 4. Сворачиваем таблицу по ключевым полям.
// Колонки "СуммаПлюс" и "СуммаМинус" будут удалены автоматически.
ТаблицаДанных.Свернуть("Номенклатура, Склад", "СуммаИтого");
Данный метод работает чрезвычайно быстро на небольших объемах данных (до 5 000 – 10 000 строк). Он прост в отладке и не требует обращения к СУБД.
Когда количество строк в таблице превышает десятки тысяч, обход в цикле на встроенном языке 1С может начать уступать по производительности механизмам СУБД. В этом случае эффективнее передать таблицу значений в запрос, произвести там все математические операции и сгруппировать результат.
Выясним технические особенности выполнения этой операции:
&ВнешняяТЗ), абсолютно все её колонки, которые будут использоваться в запросе, должны быть жестко типизированы при создании таблицы. Если хотя бы у одной колонки тип будет составным (содержащим Неопределено) или вообще не задан, платформа выбросит исключение на этапе выполнения запроса.Посмотрим на пример реализации этого метода:
// Создаем запрос для обработки таблицы значений
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВремТЗ.Номенклатура КАК Номенклатура,
| ВремТЗ.Склад КАК Склад,
| ВремТЗ.СуммаПлюс - ВремТЗ.СуммаМинус КАК СуммаИтого
|ПОМЕСТИТЬ ВтДанные
|ИЗ
| &ВнешняяТЗ КАК ВремТЗ
|;
|
|ВЫБРАТЬ
| ВтДанные.Номенклатура КАК Номенклатура,
| ВтДанные.Склад КАК Склад,
| СУММА(ВтДанные.СуммаИтого) КАК СуммаИтого
|ИЗ
| ВтДанные КАК ВтДанные
|
|СГРУППИРОВАТЬ ПО
| ВтДанные.Номенклатура,
| ВтДанные.Склад";
// Устанавливаем таблицу в качестве параметра.
// Помним: колонки Номенклатура, Склад, СуммаПлюс, СуммаМинус должны быть жестко типизированы!
Запрос.УстановитьПараметр("ВнешняяТЗ", ТаблицаДанных);
// Выполняем запрос и выгружаем результат в новую свернутую таблицу
ТаблицаСвернутая = Запрос.Выполнить().Выгрузить();
Система компоновки данных — крайне мощный инструмент, но применять его нужно обдуманно. Рассмотрим ситуации, в которых использование СКД оправдано, а в каких — избыточно.
СуммаПлюс - СуммаМинус. После этого новое поле назначается ресурсом со значением Сумма(ВычисляемоеПоле). Платформа самостоятельно сгруппирует и свернет данные при выводе отчета. Это избавляет разработчика от написания рутинного кода по группировке.При проектировании кода важно помнить о распределении контекста между клиентом и сервером в управляемом приложении:
В управляемых формах (Тонкий клиент, Веб-клиент) объект типа ТаблицаЗначений физически отсутствует на стороне клиента. Вместо него на клиенте данные хранятся в специальном объекте-оболочке ДанныеФормыКоллекция (или ДанныеФормыДерево). Этот объект имеет существенно ограниченный набор методов — у него нет встроенного метода Свернуть().
Поэтому, если ваши данные находятся в таблице на форме (на клиенте), вам в любом случае придется передать их на сервер (через вызов серверной процедуры), преобразовать коллекцию в полноценную таблицу значений, выполнить свертку (через цикл или запрос) и вернуть результат обратно на клиент.
Проанализируем пример схемы передачи данных:
&НаКлиенте
Процедура ОбработатьТаблицуНаКлиенте()
// На клиенте метод Свернуть() вызвать нельзя. Передаем данные на сервер.
СвернутьТаблицуНаСервере();
КонецПроцедуры
&НаСервере
Процедура СвернутьТаблицуНаСервере()
// 1. Конвертируем данные формы в физическую ТаблицуЗначений
ТаблицаОбъект = ДанныеФормыВЗначение(Объект.ИмяТабличнойЧасти, Тип("ТаблицаЗначений"));
// 2. Добавляем колонку с типом
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("Число"));
ОписаниеЧисла = Новый ОписаниеТипов(МассивТипов, , , Новый КвалификаторыЧисла(15, 2));
ТаблицаОбъект.Колонки.Добавить("СуммаИтого", ОписаниеЧисла);
// 3. Вычисляем результат в цикле
Для Каждого Строка Из ТаблицаОбъект Цикл
Строка.СуммаИтого = Строка.СуммаПлюс - Строка.СуммаМинус;
КонецЦикла;
// 4. Сворачиваем данные
ТаблицаОбъект.Sвернуть("Номенклатура", "СуммаИтого");
// 5. Возвращаем результат обратно в реквизит формы
ЗначениеВДанныеФормы(ТаблицаОбъект, Объект.ИмяТабличнойЧасти);
КонецПроцедуры
Мы подробно разобрали задачу объединения двух числовых колонок в одну при свертке таблицы значений. Для большинства повседневных задач оптимальным выбором будет Способ 1 (Цикл + Свернуть) с обязательным указанием типа добавляемой колонки — это просто, быстро и надежно. Если же вам приходится оперировать большими массивами данных (от 10 000 строк) в клиент-серверном варианте, имеет смысл использовать Способ 2 (Запрос). Использовать СКД стоит только тогда, когда итоговые свернутые данные предназначаются для непосредственного вывода пользователю в отчет.