При работе с языком запросов 1С:Предприятие программисты часто сталкиваются с ситуацией, когда использование агрегатной функции СРЕДНЕЕ в секции ИТОГИ выдает результат, подозрительно похожий на обычную сумму. Особенно часто это проявляется при работе с виртуальными таблицами регистров накопления и использованием конструкций дополнения периодов. В рамках этой статьи мы разберем, почему так происходит, проанализируем механизм расчета итогов и выясним, как получить корректные данные.
Рассмотрим ситуацию на конкретном примере запроса к виртуальной таблице оборотов. Программист пытается получить данные о продажах с расчетом среднего количества и среднего тарифа, используя дополнение по месяцам:
ВЫБРАТЬ
CRM_ПродажиОбороты.ПериодМесяц КАК ПериодМесяц,
CRM_ПродажиОбороты.ПериодГод КАК ПериодГод,
CRM_ПродажиОбороты.КоличествоОборот КАК КоличествоОборот,
ЕстьNULL(CRM_ПродажиОбороты.ЕЦГ_ТарифОборот, 0) КАК ЕЦГ_ТарифОборот
ИЗ
РегистрНакопления.CRM_Продажи.Обороты(&ДатаНач, &ДатаКон, Авто, Партнер В (&Партнеры)) КАК CRM_ПродажиОбороты
ИТОГИ
СРЕДНЕЕ(КоличествоОборот),
СРЕДНЕЕ(ЕЦГ_ТарифОборот)
ПО
ПериодГод ПЕРИОДАМИ(МЕСЯЦ, , ),
ПериодМесяц
В данном случае для ресурса КоличествоОборот расчет идет верно, а для поля ЕЦГ_ТарифОборот результат совпадает с суммой. Выясним причину такого поведения системы.
Первое, что мы должны учитывать: виртуальная таблица Обороты — это уже агрегированные данные. В отличие от физической таблицы (таблицы движений), где каждая запись соответствует документу, в виртуальной таблице данные сгруппированы по измерениям и выбранному периоду (в нашем случае — Авто, что в контексте ПериодМесяц означает группировку по месяцам).
Когда мы применяем СРЕДНЕЕ в секции ИТОГИ к полям виртуальной таблицы, система рассчитывает среднее арифметическое от **уже просуммированных** значений. Проанализируем разницу:
Именно поэтому результат кажется "суммой" — если в периоде группировки (например, в году) только один месяц с данными, среднее от одного значения будет равно этому самому значению (сумме за месяц).
Конструкция ПЕРИОДАМИ(МЕСЯЦ, , ) критически влияет на расчет среднего. Она заставляет платформу 1С "дорисовывать" в выборке недостающие месяцы, по которым не было движений. Рассмотрим, как это меняет математику процесса:
Если в году продажи были только в марте (сумма 120), то:
Многие разработчики ошибочно ожидают, что система будет игнорировать пустые месяцы при расчете среднего, но механизм ИТОГИ считает количество строк в сформированной выборке. Если ПЕРИОДАМИ добавило строки с нулевыми значениями, они станут полноценными участниками знаменателя в формуле среднего арифметического.
В процессе обсуждения часто возникает вопрос о наличии значений NULL в числовых ресурсах. Рассмотрим этот момент подробнее. В платформе 1С ресурсы регистров накопления имеют числовой тип и физически в базе данных хранятся как числа (по умолчанию 0). Значение NULL не может храниться в ресурсе регистра накопления.
Однако NULL может появиться в результате работы запроса:
ЛЕВОЕ СОЕДИНЕНИЕ или ПОЛНОЕ СОЕДИНЕНИЕ.Важно помнить: функция СРЕДНЕЕ в SQL (и в 1С) игнорирует NULL при расчете знаменателя, но учитывает значение 0. Если мы используем ЕстьNULL(Тариф, 0), мы принудительно превращаем отсутствующие записи в нули. В результате знаменатель растет, а сумма остается прежней, что резко занижает итоговое среднее значение.
Чтобы убедиться, что проблема не в данных, а в методе обращения к ним, проанализируем запрос к реальным записям регистра (физической таблице):
ВЫБРАТЬ
CRM_Продажи.Партнер КАК Партнер,
СУММА(CRM_Продажи.ЕЦГ_Тариф) КАК ЕЦГ_Тариф,
СРЕДНЕЕ(CRM_Продажи.ЕЦГ_Тариф) КАК ЕЦГ_Тариф1,
СУММА(CRM_Продажи.Количество) КАК Количество,
СРЕДНЕЕ(CRM_Продажи.Количество) КАК Количество1
ИЗ
РегистрНакопления.CRM_Продажи КАК CRM_Продажи
ГДЕ
CRM_Продажи.Партнер = &Партнер
И CRM_Продажи.Период >= &ДатаНач
И CRM_Продажи.Период <= &ДатаКон
СГРУППИРОВАТЬ ПО
CRM_Продажи.Партнер
В таком варианте расчет СРЕДНЕЕ будет выполняться на уровне СУБД по каждой отдельной записи регистра (по каждому документу). Результат здесь будет технически корректным средним арифметическим всех движений. Разница в результатах между этим запросом и запросом к Обороты наглядно доказывает, что проблема кроется в предварительной агрегации данных виртуальной таблицей.
Для получения корректного среднего значения в отчетах рекомендуется придерживаться следующих правил:
СРЕДНЕЕ, рассчитайте в запросе отдельно СУММА(Ресурс) и КОЛИЧЕСТВО(Различные ПериодМесяц). Затем в коде или в вычисляемых полях СКД разделите одно на другое. Это даст вам полный контроль над тем, что именно является знаменателем.Таким образом, "ошибка", при которой 1С считает сумму вместо среднего, чаще всего является следствием того, что в выборку попадает только одна агрегированная строка из виртуальной таблицы, либо результатом специфического поведения платформы при дополнении периодов.