При построении аналитических отчетов и расчете средних показателей (например, среднемесячного расхода, среднедневных продаж или оборачиваемости) разработчики часто сталкиваются с необходимостью деления агрегированных сумм на интервал времени. Однако попытка разделить числовое поле напрямую на выражение с функцией РАЗНОСТЬДАТ нередко приводит к ошибкам выполнения запроса или некорректным результатам. Разберем по шагам, почему возникают эти ошибки и как правильно составить запрос.
Рассмотрим выражение, вызывающее ошибку:
ТоварыНаСкладахОстаткиИОбороты.КоличествоРасход / КОЛИЧЕСТВО(РАЗНОСТЬДАТ(&ДатаНач, КОНЕЦПЕРИОДА(&ДатаКон, ДЕНЬ), МЕСЯЦ))
В приведенной конструкции допущены сразу две принципиальные ошибки:
КОЛИЧЕСТВО. В языке запросов 1С функция КОЛИЧЕСТВО(...) является аналогом SQL-агрегата COUNT. Она предназначена для подсчета числа строк в выборке или группировке. Оборачивать в нее скалярное вычисление разности дат между двумя параметрами бессмысленно и синтаксически неверно.РАЗНОСТЬДАТ. Функция РАЗНОСТЬДАТ подсчитывает не фактическую временную продолжительность, а количество переходов через границы указанного периода. Если дата начала и дата окончания находятся внутри одного календарного месяца (например, с 01.03.2024 по 31.03.2024), функция РАЗНОСТЬДАТ(&ДатаНач, &ДатаКон, МЕСЯЦ) вернет 0. При попытке деления СУБД генерирует исключение «Деление на ноль».Если требуется рассчитать среднемесячный расход с учетом того, что границы периода могут задаваться в рамках одного или нескольких месяцев, необходимо исключить деление на ноль. Разберем два способа реализации.
Первый способ — инкремент количества месяцев, если границы отчета всегда задаются от начала первого месяца до конца последнего:
ВЫБРАТЬ
ТоварыНаСкладахОстаткиИОбороты.Номенклатура КАК Номенклатура,
СУММА(ТоварыНаСкладахОстаткиИОбороты.КоличествоРасход) КАК КоличествоРасход,
СУММА(ТоварыНаСкладахОстаткиИОбороты.КоличествоРасход) / (РАЗНОСТЬДАТ(&ДатаНач, &ДатаКон, МЕСЯЦ) + 1) КАК СреднееВМесяц
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&ДатаНач, &ДатаКон, , , ) КАК ТоварыНаСкладахОстаткиИОбороты
СГРУППИРОВАТЬ ПО
ТоварыНаСкладахОстаткиИОбороты.Номенклатура
Второй способ — явная проверка через конструкцию ВЫБОР КОГДА. Этот подход универсален и предотвращает деление на ноль при любых переданных параметрах дат:
ВЫБОР
КОГДА РАЗНОСТЬДАТ(&ДатаНач, &ДатаКон, МЕСЯЦ) = 0
ТОГДА СУММА(ТоварыНаСкладахОстаткиИОбороты.КоличествоРасход)
ИНАЧЕ СУММА(ТоварыНаСкладахОстаткиИОбороты.КоличествоРасход) / (РАЗНОСТЬДАТ(&ДатаНач, &ДатаКон, МЕСЯЦ) + 1)
КОНЕЦ КАК СреднееВМесяц
Если отчет формируется за произвольный период, не кратный целому месяцу (например, за 10 дней или полтора месяца), вычисление разности в месяцах дает существенную погрешность. В таких задачах надежнее считать интервал в днях:
ВЫБРАТЬ
ТоварыНаСкладах.Номенклатура КАК Номенклатура,
СУММА(ТоварыНаСкладах.КоличествоРасход) КАК КоличествоРасход,
// Среднедневной расход
СУММА(ТоварыНаСкладах.КоличествоРасход) / (РАЗНОСТЬДАТ(&ДатаНач, КОНЕЦПЕРИОДА(&ДатаКон, ДЕНЬ), ДЕНЬ) + 1) КАК СреднееВДень,
// Приведение к среднемесячному расходу (через усредненный коэффициент 30.4375 или 30)
(СУММА(ТоварыНаСкладах.КоличествоРасход) / (РАЗНОСТЬДАТ(&ДатаНач, КОНЕЦПЕРИОДА(&ДатаКон, ДЕНЬ), ДЕНЬ) + 1)) * 30.4375 КАК СреднемесячныйРасход
ИЗ
РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&ДатаНач, &ДатаКон, , , ) КАК ТоварыНаСкладах
СГРУППИРОВАТЬ ПО
ТоварыНаСкладах.Номенклатура
Проанализируем сопутствующие моменты, на которые важно обращать внимание при написании подобных запросов:
СГРУППИРОВАТЬ ПО, все числовые показатели, не входящие в группировку, обязательно должны быть обернуты в агрегатные функции, например СУММА(ТоварыНаСкладахОстаткиИОбороты.КоличествоРасход).Таблица1.Номенклатура.Ссылка = Таблица2.Номенклатура.Ссылка создает неявное обращение к таблице справочника. Правильно связывать поля напрямую: Таблица1.Номенклатура = Таблица2.Номенклатура.Авто или ПериодМесяц) их необходимо связывать не только по номенклатуре, но и по полю периода, чтобы избежать эффекта декартова перемножения строк.