При разработке отчетов в системе компоновки данных (СКД) программисты часто сталкиваются с проблемой: стандартная функция Среднее для ресурсов рассчитывает значение неверно. Это происходит потому, что виртуальные таблицы остатков и оборотов возвращают данные только за те периоды, в которых были совершены движения. Если в течение месяца или дня операций по складу не было, строка в результат запроса не попадает, и СКД «выбрасывает» этот период из расчета среднего. В итоге вместо среднего арифметического за весь интервал мы получаем среднее только по тем дням, когда товар перемещался.
Рассмотрим ситуацию на примере: нам нужно посчитать средний остаток за 4 месяца. В первом месяце остаток был 699, во втором — 699, в третьем — 474, а в четвертом движений не было вообще. Ожидаемый результат: (699 + 699 + 474 + 474) / 4 = 586.5. Однако СКД выдаст (699 + 699 + 474) / 3 = 624, так как четвертый месяц просто не существует в наборе данных. Давайте разберем, как решить эту задачу несколькими способами.
Прежде всего проанализируем настройки ролей в наборе данных. Чтобы СКД понимала, что она работает с остатками, необходимо правильно заполнить колонку Роли для поля с остатком. Если этого не сделать, система будет воспринимать поле как обычное число, что приведет к ошибкам при агрегации в группировках.
Остаток (или КоличествоНачальныйОстаток) установим роль Остаток.Количество), выберем Тип (Начальный) и обязательно укажем Поле периода (например, ПериодМесяц).Месяц (или День, в зависимости от задачи).Важный нюанс: даже при включении дополнения периодов стандартный ресурс Среднее(Остаток) может продолжать работать некорректно. Это связано с тем, что вычисление среднего происходит на этапе получения данных из БД, а дополнение периодов — это визуальный механизм компоновки. Поэтому перейдем к более надежным методам.
Наиболее точный и надежный способ — подготовить данные в самом запросе так, чтобы в нем присутствовали записи за каждый период (день или месяц) выбранного интервала. Для этого мы воспользуемся производственным календарем. Рассмотрим этот алгоритм по шагам.
Проанализируем пример кода, который формирует таблицу со всеми месяцами и заполняет остатки на каждую дату:
// Шаг 1: Формируем таблицу всех необходимых дат из календаря
ВЫБРАТЬ РАЗЛИЧНЫЕ
ДанныеПроизводственногоКалендаря.Дата КАК Дата
ПОМЕСТИТЬ Календарь
ИЗ
РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
ГДЕ
ДанныеПроизводственногоКалендаря.Дата МЕЖДУ &НачПериода И &КонПериода
И ДанныеПроизводственногоКалендаря.Дата = НАЧАЛОПЕРИОДА(ДанныеПроизводственногоКалендаря.Дата, МЕСЯЦ)
;
// Шаг 2: Получаем реальные остатки и обороты из регистра
ВЫБРАТЬ
ТоварыНаСкладахОстаткиИОбороты.Период КАК Период,
ТоварыНаСкладахОстаткиИОбороты.ВНаличииКонечныйОстаток КАК ВНаличииКонечныйОстаток
ПОМЕСТИТЬ ТЗ
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты({(&НачПериода)}, {(&КонПериода)}, Месяц, ДвиженияИГраницыПериода, ) КАК ТоварыНаСкладахОстаткиИОбороты
;
// Шаг 3: Сопоставляем каждую дату календаря с последним известным периодом из данных
ВЫБРАТЬ
Календарь.Дата КАК Дата,
МАКСИМУМ(ТЗ.Период) КАК Период
ПОМЕСТИТЬ Периоды
ИЗ
Календарь КАК Календарь
ЛЕВОЕ СОЕДИНЕНИЕ ТЗ КАК ТЗ
ПО Календарь.Дата >= ТЗ.Период
СГРУППИРОВАТЬ ПО
Календарь.Дата
;
// Шаг 4: Финальный расчет с получением остатка на каждую дату календаря
ВЫБРАТЬ
Периоды.Дата КАК Месяц,
ЕСТЬNULL(ТЗОстатки.ВНаличииКонечныйОстаток, 0) КАК СреднийОстаток
ИЗ
Периоды КАК Периоды
ЛЕВОЕ СОЕДИНЕНИЕ ТЗ КАК ТЗОстатки
ПО Периоды.Период = ТЗОстатки.Период
В чем преимущество такого подхода? Мы создаем непрерывную временную шкалу. Если в феврале движений не было, запрос найдет «максимальный период, который меньше или равен февралю» (это будет январь) и возьмет конечный остаток января как остаток на февраль. Теперь, когда в итоговой таблице есть строка для каждого месяца, обычная функция Среднее в ресурсах СКД сработает идеально.
Если мы не хотим значительно усложнять запрос, можно воспользоваться встроенным языком выражений СКД. Выясним причину ошибки: нам не хватает общего количества периодов в знаменателе. Мы можем заменить стандартную функцию Среднее на собственную формулу в поле ресурса.
Рассмотрим формулу для ресурса:
Сумма(Остаток) / (РазностьДат(&НачалоПериода, &КонецПериода, "МЕСЯЦ") + 1)
Однако этот метод сработает только в том случае, если сумма всех остатков в запросе корректна. Для более гибкого расчета в группировках лучше использовать функцию ВычислитьВыражение. Она позволяет получить массив значений остатков по всем группировкам, включая «пустые» периоды, если настроено дополнение.
Пример выражения для ресурса:
Среднее(ВычислитьВыражениеСГруппировкойМассив("Сумма(Остаток)", "ПериодМесяц"))
Посмотрим, как это работает: СКД сначала обходит все периоды (благодаря дополнению), собирает суммы остатков по каждому в массив, а затем вычисляет среднее значение этого массива. Это современный и лаконичный способ решения проблемы без «раздувания» запроса временными таблицами.
В бухгалтерском и финансовом учете часто используется метод средней хронологической. Он считается более точным для остатков на конкретные даты. Формула выглядит так: (1/2 ОстаткаНаНачало + ОстаткиНаКонецПромежуточныхПериодов + 1/2 ОстаткаНаКонец) / (КоличествоПериодов - 1).
Чтобы реализовать это в СКД, выполним следующие действия:
Остаток * Вес и разделим на общую сумму весов.Этот метод позволяет избежать искажений, когда остаток на начало дня и конец дня сильно отличается, обеспечивая наиболее «справедливое» среднее значение для финансовой отчетности.
Проанализировав ситуацию, мы видим, что проблема «неправильного» среднего в СКД — это следствие оптимизации работы с данными в 1С. Система не хранит информацию о том, что «ничего не изменилось», поэтому пустые периоды нужно добавлять принудительно.
Для простых отчетов достаточно правильно настроить Роли полей и Тип дополнения. Для сложных и точных отчетов рекомендуем использовать Метод 2 (с календарем) или Метод 3 (с ВычислитьВыражение). Выбор зависит от ваших требований к производительности и сложности структуры данных.