Как сделать группировку номенклатуры без запросов и СКД в 1С

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

В практике разработки на платформе «1С:Предприятие» стандартным и наиболее эффективным способом получения сгруппированных данных является использование запросов или системы компоновки данных (СКД). Однако возникают задачи, когда необходимо выполнить группировку (агрегацию) данных непосредственно в оперативной памяти «на лету», используя только встроенный язык. Например, при обработке небольших объемов данных из выборки документов или при работе в простых локальных обработках.

В рамках этой статьи мы детально разберем, как реализовать группировку номенклатуры по количеству и сумме без использования механизмов СУБД, рассмотрим типичные ошибки начинающих разработчиков и покажем сопутствующие приемы фильтрации документов по периодам и статусу проведения.

Шаг 1. Сбор данных во временную таблицу значений

Для того чтобы сгруппировать данные по номенклатуре, сначала необходимо собрать их в один массив. Лучшим объектом для этой задачи в памяти является ТаблицаЗначений. Мы создадим таблицу, опишем ее структуру и заполним строками из документов.

Важная деталь: Часто разработчики совершают ошибку, пытаясь добавить в таблицу значений колонки со сложными путями (например, через точку). Код вида ТаблицаТоваров.Колонки.Добавить("Ссылка.Дата") вызовет ошибку выполнения «Неверное имя колонки». Имя колонки должно быть простым идентификатором, а значение (например, дату документа) мы будем присваивать непосредственно при обходе выборки.

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


ТаблицаТоваров = Новый ТаблицаЗначений;
ТаблицаТоваров.Колонки.Добавить("Номенклатура");
ТаблицаТоваров.Колонки.Добавить("Количество");
ТаблицаТоваров.Колонки.Добавить("Сумма");
ТаблицаТоваров.Колонки.Добавить("Дата");

// Получаем выборку документов за определенный период
Выборка = Документы.РеализацияТоваровУслуг.Выбрать(Отчет.ПериодНачало, Отчет.ПериодКонец);

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

Шаг 2. Группировка (свертка) данных

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

  1. Колонки группировки — список колонок, по которым строки будут объединены (значения в этих колонках должны совпадать).
  2. Колонки суммирования — список колонок, числовые значения которых будут сложены при объединении строк.

Проанализируем ситуацию с расчетом цены. Нельзя добавлять колонку «Цена» в список суммируемых колонок. При объединении одинаковых товаров их цены просто сложатся, что приведет к некорректным данным. Правильный подход — рассчитывать цену как средневзвешенную (Сумма / Количество) уже после выполнения свертки при выводе данных в отчет.

Посмотрим на пример выполнения свертки и последующего вывода данных в табличный документ:


// Сворачиваем таблицу по номенклатуре, суммируя количество и сумму
ТаблицаТоваров.Свернуть("Номенклатура", "Количество, Сумма");

// Рекомендуется отсортировать результат для удобства чтения отчета
ТаблицаТоваров.Сортировать("Номенклатура Возр");

НомерСтроки = 0;
СуммаВсего = 0;

Для Каждого СтрокаВыборки Из ТаблицаТоваров Цикл
    НомерСтроки = НомерСтроки + 1;
    
    ОбластьСтрока.Параметры.НомерСтроки = НомерСтроки;
    ОбластьСтрока.Параметры.Товар       = СтрокаВыборки.Номенклатура;
    ОбластьСтрока.Параметры.Количество  = СтрокаВыборки.Количество;
    
    // Рассчитываем цену с проверкой деления на ноль
    Если СтрокаВыборки.Количество <> 0 Тогда
        ОбластьСтрока.Параметры.Цена = СтрокаВыборки.Сумма / СтрокаВыборки.Количество;
    Иначе
        ОбластьСтрока.Параметры.Цена = 0;
    КонецЕсли;
    
    ОбластьСтрока.Параметры.Сумма = СтрокаВыборки.Сумма;
    СуммаВсего = СуммаВсего + СтрокаВыборки.Сумма;
    
    ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;

Шаг 3. Обработка неограниченного периода и корректных границ дат

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

Если использовать простую передачу параметров, документы на границе периода могут не попасть в выборку. Чтобы этого избежать, используем функцию КонецДня() для конечной даты. Также применим тернарный оператор (сокращенное условие ?()), чтобы корректно отрабатывать ситуацию, когда пользователь оставил дату окончания пустой:


ДатаНачала = Отчет.ПериодНачало;
// Если дата окончания не задана, передаем пустую дату, иначе берем самый конец дня
ДатаКонца = ?(ЗначениеЗаполнено(Отчет.PeriodКонец), КонецДня(Отчет.ПериодКонец), Отчет.ПериодКонец);

Выборка = Документы.РеализацияТоваровУслуг.Выбрать(ДатаНачала, ДатаКонца);

Для визуального оформления отчета мы можем проверять, заполнен ли период в форме, и выводить текстовое представление интервала только тогда, когда параметры действительно заданы пользователем:


Если ЗначениеЗаполнено(Отчет.ПериодНачало) Или ЗначениеЗаполнено(Отчет.ПериодКонец) Тогда
    ОбластьШапка.Параметры.ПериодОтчета = "Период с " + Формат(Отчет.ПериодНачало, "ДФ=dd.MM.yyyy") + " по " + Формат(Отчет.ПериодКонец, "ДФ=dd.MM.yyyy");
Иначе
    ОбластьШапка.Параметры.ПериодОтчета = "За весь период";
КонецЕсли;

Альтернативный способ группировки в памяти: использование соответствия (Map)

Хотя метод Свернуть() для ТаблицыЗначений очень прост в реализации, у него есть архитектурный недостаток: мы вынуждены сначала загрузить абсолютно все строки из всех документов в память (что создает дубли и увеличивает расход ОЗУ), и только потом выполнять свертку.

Если обрабатывается большое количество документов, более производительным решением будет использование объекта Соответствие. В этом случае мы агрегируем данные прямо во время обхода выборки, не создавая промежуточных дублирующихся строк:


// Ключ соответствия — Номенклатура, Значение — Структура с показателями
ДанныеГруппировки = Новый Соответствие;

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

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

Ограничения и рекомендации платформы

Необходимо помнить, что обход документов через метод Выбрать() и ручная группировка в памяти — это тяжелая операция для СУБД и сервера приложений 1С. Метод Выбрать() извлекает из базы данных объекты документов целиком (все реквизиты, все табличные части, движения), даже если вам нужны только три поля.

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

← На главную