Как сгруппировать записи в регистре накопления по общей сумме при проведении документа

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке конфигураций на платформе 1С:Предприятие часто возникает ситуация, когда в табличной части документа содержится множество строк, которые при проведении должны сформировать всего одну или несколько обобщенных записей в регистре накопления. Это необходимо для оптимизации объема базы данных, ускорения работы отчетов и повышения производительности системы в целом. В данной статье мы разберем, как изменить стандартный алгоритм проведения, чтобы вместо дублирования строк из документа в регистр записывались уже агрегированные (сгруппированные) данные.

Зачем нужна группировка записей в регистрах

Рассмотрим ситуацию: пользователь вводит документ, в котором указано 50 строк с одним и тем же товаром, но разными характеристиками или просто по разным ценам, которые в итоге должны упасть на один общий остаток. Если записывать каждую строку отдельно, таблица движений регистра будет расти в геометрической прогрессии. Проанализируем последствия избыточного количества записей:

Для решения этой задачи нам необходимо вмешаться в процедуру ОбработкаПроведения модуля документа.

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

Самый простой и наглядный способ для начинающего разработчика — это выгрузка данных табличной части в объект типа ТаблицаЗначений, их последующая обработка и загрузка в набор записей регистра. Рассмотрим этот процесс по шагам.

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


// В модуле документа
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Подготавливаем таблицу значений
    ТаблицаДляДвижений = Товары.Выгрузить( , "Склад, Сумма");
    
    // 2. Группируем данные
    // Первый параметр - колонки, по которым группируем (Измерения)
    // Второй параметр - колонки, которые суммируем (Ресурсы)
    ТаблицаДляДвижений.Свернуть("Склад", "Сумма");
    
    // 3. Записываем результат в регистр
    Для Каждого СтрокаДанных Из ТаблицаДляДвижений Цикл
        Движение = Движения.ОстаткиТоваров.Добавить();
        Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
        Движение.Период = Дата;
        Движение.Склад = СтрокаДанных.Склад;
        Движение.Сумма = СтрокаДанных.Сумма;
    КонецЦикла;

КонецПроцедуры

Разберем ситуацию подробнее: метод Свернуть объединяет все строки, у которых значения в колонках группировки (в нашем примере это Склад) совпадают. Если нам нужно получить ровно одну строку на весь документ (как просил автор вопроса), то в параметрах группировки вообще не должно быть уникальных значений. Если мы оставим первый параметр метода пустым, ТаблицаДляДвижений.Свернуть("", "Сумма"), то на выходе получим одну строку с общей суммой по всему документу.

Метод 2: Агрегация данных с помощью запроса (рекомендуемый)

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

Проанализируем пример кода, в котором мы используем менеджер временных таблиц для передачи данных табличной части в запрос:


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

КонецПроцедуры

Обратим внимание на оператор СГРУППИРОВАТЬ ПО. Именно он отвечает за то, чтобы записи с одинаковыми складами "схлопнулись". Внутри секции ВЫБРАТЬ мы обязательно используем агрегатную функцию СУММА для всех числовых ресурсов. Этот метод позволяет избежать лишних итераций и эффективнее работает с памятью сервера.

Важное ограничение: Роль Измерений регистра

Выясним причину, по которой группировка может "не сработать" так, как ожидает разработчик. В регистре накопления есть Измерения и Ресурсы. Группировка возможна только в том случае, если значения всех измерений в объединяемых строках идентичны.

Пример: Если в регистре есть измерение Номенклатура, а в документе выбраны два разных товара (Яблоко и Груша), вы физически не сможете объединить их в одну запись в регистре. Система обязана знать, сколько остатка числится за каждым конкретным товаром. Если ваша цель — одна запись на документ с общей суммой, то в регистре либо не должно быть измерений (что редкость), либо в эти измерения должны записываться общие для всего документа значения (например, Контрагент или Склад).

Оптимизация через Набор Записей

Рассмотрим еще один продвинутый прием. Вместо метода Добавить() в цикле, можно подготовить таблицу значений запросом и загрузить её целиком в набор записей. Это работает быстрее, так как платформа выполняет запись пакетом.


// Получаем свернутую таблицу запросом
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();

// Загружаем данные напрямую в регистр
Движения.ОстаткиТоваров.Записывать = Истина;
Движения.ОстаткиТоваров.Загрузить(ТаблицаРезультат);

При использовании метода Загрузить() важно, чтобы имена колонок в таблице значений полностью совпадали с именами измерений и ресурсов регистра. Посмотрим на этот процесс как на сопоставление структур: если в регистре поле называется Стоимость, а в запросе вы назвали его Сумма, данные не загрузятся. Используйте псевдонимы в запросе (КАК Стоимость), чтобы добиться соответствия.

Типичные ошибки при реализации группировки

  1. Забытая очистка движений: Если не установить Движения.ИмяРегистра.Записывать = Истина или не очистить движения перед записью, при повторном проведении старые записи могут дублироваться или накладываться на новые.
  2. Группировка по уникальным полям: Если вы включите в группировку НомерСтроки табличной части, группировка не произойдет, так как номер строки уникален для каждой позиции.
  3. Потеря точности: При агрегации данных большого объема следите за типами данных в ресурсах регистра, чтобы сумма всех строк документа не превысила максимально допустимое числовое значение поля.

Подводя итог, скажем: чтобы в регистре накопления появлялась только одна запись, необходимо агрегировать данные табличной части до момента записи. Мы проанализировали два основных пути: через метод Свернуть() и через запрос с оператором СГРУППИРОВАТЬ ПО. Второй вариант является более предпочтительным в современной разработке на платформе 1С.

← На главную