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