Как эффективно получить итоги по движениям документа в 1С:Бухгалтерии 2.0?

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Бухгалтерский учет
← На главную

При работе с документами в системе 1С, особенно с теми, которые генерируют большое количество движений по регистрам, например, как "Отчет производства за смену", часто возникает необходимость получить агрегированные данные по этим движениям. К таким задачам относится, например, подсчет общей суммы или количества по определенному регистру, либо выполнение более специфичных агрегаций с отбором по конкретным параметрам, таким как номенклатура. Использование простого перебора всех движений в цикле, особенно при тысячах записей, является крайне неэффективным и может привести к значительным задержкам в работе программы.

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

Решение 1: Получение итогов по движениям документа после его проведения с помощью запроса

Когда документ уже успешно проведен, и все его движения по регистрам были записаны в базу данных, самым гибким и высокопроизводительным способом получения агрегированных данных является использование механизма запросов 1С. Этот подход позволяет платформе делегировать трудоемкие операции по агрегации данных непосредственно системе управления базами данных (СУБД), что гарантирует оптимальную скорость выполнения даже при очень больших объемах информации.

Давайте подробно разберем шаги по формированию и выполнению такого запроса:

  1. Создание объекта Запрос. На первом шаге мы всегда инициализируем новый экземпляр объекта Запрос, который будет служить инструментом для выполнения нашей операции по выборке данных.
  2. Формирование текста запроса к виртуальной таблице "Обороты". Для получения итогов по регистрам накопления, таким как "ПартииТоваровНаСкладахБУ", мы обращаемся к их виртуальным таблицам. Виртуальная таблица Обороты специально предназначена для получения агрегированных данных (приход, расход, итоговый оборот) по измерениям регистра за указанный период. В нашем запросе мы будем выбирать предопределенные агрегатные поля, такие как КоличествоОборот и СуммаОборот, которые система автоматически рассчитывает для нас.
  3. Ограничение выборки по регистратору. Крайне важно ограничить данные только движениями нашего конкретного документа "Отчет производства за смену". Для этого мы используем условие Регистратор = &Документ внутри параметров виртуальной таблицы Обороты. Это гарантирует, что мы получим итоги, относящиеся исключительно к интересующему нас документу, избегая обработки всех движений регистра за период.
  4. Установка параметров запроса. Для корректной работы запроса необходимо передать значения его параметрам. В нашем случае, это дата начала и окончания периода, которые, как правило, соответствуют дате самого документа (если нам нужны обороты за один день), а также ссылка на сам объект документа.
  5. Выполнение запроса и обработка результата. После того как запрос сформирован и все его параметры установлены, мы выполняем его. Результатом выполнения запроса будет объект Выборка, из которого мы последовательно можем извлечь полученные итоговые данные.

Рассмотрим пример программного кода, демонстрирующего получение итогов по регистру накопления "ПартииТоваровНаСкладахБУ" после проведения документа:


// Создаем новый объект запроса для работы с данными
Запрос = Новый Запрос;

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

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

// Выполняем запрос к базе данных
Результат = Запрос.Выполнить().Выбрать();

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

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

Решение 2: Получение итогов по движениям документа в процессе его проведения с помощью метода Итоги()

Второй сценарий, с которым мы часто сталкиваемся, – это необходимость подсчета итогов по движениям документа непосредственно во время его проведения. В этот момент движения уже сформированы в оперативной памяти, но еще не записаны в базу данных. В такой ситуации стандартные запросы к базе данных не применимы, поскольку данные физически отсутствуют в таблицах регистров. Для решения этой задачи платформа 1С предоставляет специализированный и очень производительный механизм – метод Итоги(), доступный у объекта НаборЗаписей для регистров накопления и регистров бухгалтерии.

Давайте подробно разберем механизм работы с методом Итоги():

  1. Доступ к объекту НаборЗаписей регистра. Внутри обработчика проведения документа (например, в процедуре ОбработкаПроведения() модуля объекта документа) мы имеем доступ к коллекции движений документа через свойство Движения. Каждый элемент этой коллекции представляет собой объект НаборЗаписей для конкретного регистра, например, Движения.ПартииТоваровНаСкладахБУ. Объект НаборЗаписей является представлением записей регистра, которые формируются текущим документом.
  2. Обязательное чтение набора записей методом Прочитать(). Это очень важный шаг. Если вы работаете с объектом НаборЗаписей, который был получен из коллекции Движения, и вы не уверены, что он уже содержит актуальные данные (например, если перед этим набор очищался или создавался), обязательно вызовите метод Прочитать(). Этот метод принудительно заполняет набор записей всеми данными, которые были сформированы в текущем сеансе проведения документа, гарантируя, что метод Итоги() будет работать с полной и актуальной информацией.
  3. Использование метода Итоги(). Метод Итоги() позволяет выполнить агрегацию данных по текущему набору записей. Он возвращает Структуру или Соответствие (в зависимости от параметров вызова), содержащее агрегированные значения по указанным полям. Этот метод выполняется очень быстро, поскольку платформа оптимизирована для таких операций с данными в памяти.
  4. Параметры метода Итоги():
    • Вызов без параметров: Если вызвать Итоги() без каких-либо аргументов, метод вернет общие итоги по всем числовым полям, которые присутствуют в наборе записей. Возвращаемое значение будет Структурой, где ключами являются имена полей, а значениями – их суммы.
    • Использование параметра отбора: Мы можем передать в метод Итоги() объект Структура, который будет выполнять роль отбора. Ключи этой Структуры должны соответствовать именам полей регистра, по которым требуется выполнить отбор, а значения – соответствующим значениям отбора. Например, таким образом можно получить итоги только по конкретной номенклатуре.
    • Указание полей итогов: Дополнительно можно указать строку, содержащую имена полей, по которым необходимо рассчитать итоги, если вам нужны не все числовые поля, а только определенные. Поля перечисляются через запятую, например, "Количество, Сумма". Это позволяет получить более специфичные агрегации.

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


// Предполагается, что мы находимся в модуле объекта документа, например, в процедуре ОбработкаПроведения()

// Получаем набор записей регистра накопления "ПартииТоваровНаСкладахБУ"
// Это коллекция движений, которые создает текущий документ в процессе проведения
НаборЗаписейПартий = Движения.ПартииТоваровНаСкладахБУ;

// Обязательно вызываем метод Прочитать(), чтобы гарантировать, что набор записей заполнен
// актуальными данными, сформированными текущим проведением документа.
НаборЗаписейПартий.Прочитать();

// --- Сценарий 1: Получение общего итога по всем движениям, созданным документом ---
// Вызываем метод Итоги() без параметров. Он вернет структуру с общими суммами
// по всем числовым полям набора записей.
СтруктураОбщихИтогов = НаборЗаписейПартий.Итоги();

// Доступ к полученным итогам осуществляется через имена полей структуры
Сообщить("Общая сумма по регистру ПартииТоваровНаСкладахБУ: " + СтруктураОбщихИтогов.Сумма);
Сообщить("Общее количество по регистру ПартииТоваровНаСкладахБУ: " + СтруктураОбщихИтогов.Количество);
Сообщить("------------------------------------------------------------------");

// --- Сценарий 2: Получение итогов с отбором по конкретной номенклатуре ---
// Сначала найдем ссылку на нужную номенклатуру
МояНоменклатура = Справочники.Номенклатура.НайтиПоКоду("00001"); // Пример: ищем номенклатуру по ее коду

Если МояНоменклатура <> Неопределено Тогда
    // Создаем структуру для отбора. Ключ - имя измерения регистра, значение - требуемое значение.
    ОтборПоНоменклатуре = Новый Структура("Номенклатура", МояНоменклатура);

    // Вызываем Итоги() с параметром отбора и явно указываем, по каким полям нужны итоги.
    // Если полей несколько, перечисляем их через запятую в строке.
    СтруктураИтоговСОтбором = НаборЗаписейПартий.Итоги(ОтборПоНоменклатуре, "Количество, Сумма");

    // Обрабатываем полученные итоги, проверяя наличие полей
    Если СтруктураИтоговСОтбором.Свойство("Сумма") Тогда
        Сообщить("Сумма по номенклатуре '" + МояНоменклатура.Наименование + "': " + СтруктураИтоговСОтбором.Сумма);
        Сообщить("Количество по номенклатуре '" + МояНоменклатура.Наименование + "': " + СтруктураИтоговСОтбором.Количество);
    Иначе
        Сообщить("Итоги по указанной номенклатуре не найдены в движениях.");
    КонецЕсли;
Иначе
    Сообщить("Номенклатура с кодом '00001' не найдена в справочнике.");
КонецЕсли;
Сообщить("------------------------------------------------------------------");

// --- Сценарий 3: Получение итогов по нескольким измерениям одновременно ---
// Допустим, нам нужны итоги по комбинации 'Номенклатура' и 'Склад'
МойСклад = Справочники.Склады.НайтиПоКоду("01"); // Пример: ищем склад по коду

Если МояНоменклатура <> Неопределено И МойСклад <> Неопределено Тогда
    // Создаем структуру для отбора, указывая несколько измерений
    ОтборПоНоменклатуреИСкладу = Новый Структура();
    ОтборПоНоменклатуреИСкладу.Вставить("Номенклатура", МояНоменклатура);
    ОтборПоНоменклатуреИСкладу.Вставить("Склад", МойСклад);

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

    Если СоответствиеИтогов.Свойство("Сумма") Тогда
        Сообщить("Сумма по номенклатуре '" + МояНоменклатура.Наименование + "' на складе '" + МойСклад.Наименование + "': " + СоответствиеИтогов.Сумма);
        Сообщить("Количество по номенклатуре '" + МояНоменклатура.Наименование + "' на складе '" + МойСклад.Наименование + "': " + СоответствиеИтогов.Количество);
    Иначе
        Сообщить("Итоги по указанным номенклатуре и складу не найдены.");
    КонецЕсли;
Иначе
    Сообщить("Не удалось найти Номенклатуру или Склад для сценария с несколькими измерениями.");
КонецЕсли;
Сообщить("------------------------------------------------------------------");

// Для регистров бухгалтерии, таких как Хозрасчетный, принцип работы аналогичен:
// НаборЗаписейХозрасчетный = Движения.Хозрасчетный;
// НаборЗаписейХозрасчетный.Прочитать();
// ИтогиПоХозрасчетному = НаборЗаписейХозрасчетный.Итоги();
// Сообщить("Сумма по регистру Хозрасчетный: " + ИтогиПоХозрасчетному.Сумма);

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

Сравнение подходов и выбор оптимального решения

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

  1. Если агрегированные данные необходимы после проведения документа, то есть когда все движения уже надежно записаны в таблицы базы данных, использование запроса к виртуальной таблице "Обороты" (или другим специализированным виртуальным таблицам регистра) является наиболее предпочтительным и мощным инструментом. Запросы предоставляют широкие возможности для выполнения сложных группировок, наложения детальных отборов и эффективной агрегации данных непосредственно на уровне СУБД, обеспечивая максимальную скорость обработки даже для очень крупных наборов данных.
  2. Если же итоги требуются непосредственно в процессе проведения документа, когда движения только формируются в оперативной памяти и еще не были физически записаны в базу данных, то метод Итоги(), доступный у объекта НаборЗаписей, является оптимальным решением. Он предоставляет быстрый, удобный и производительный способ агрегировать данные по сформированным, но еще не сохраненным движениям, что позволяет избежать медленного и ресурсоемкого программного перебора записей в цикле.

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

← На главную