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