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