Часто разработчики 1С сталкиваются с ситуацией: написан сложный пакетный запрос с временными таблицами, соединениями и расчетами. В обычной «Консоли запросов» он отрабатывает идеально, возвращая абсолютно правильные цифры. Но стоит перенести этот же запрос в схему компоновки данных (СКД) и запустить отчет — на выходе мы получаем пустые строки, неверные итоги, задвоенные или вообще нулевые показатели (как в случае автора темы, у которого «поломались» расчетные поля Флаг, Ср_Нед_Продажи_Шт и Ср_Нед_Продажи_Руб).
В этой статье мы подробно разберем, почему СКД ведет себя не так, как обычная консоль запросов, какие внутренние механизмы платформы приводят к искажению данных и как правильно перестроить запрос и настройки компоновки, чтобы отчет работал быстро и корректно.
В отличие от консоли запросов, которая отправляет СУБД исходный текст «как есть», СКД — это интеллектуальный декларативный движок. Перед отправкой запроса на выполнение компоновщик полностью перестраивает его.
СКД анализирует, какие именно поля выбраны пользователем в настройках отчета (в группировках, колонках, отборах). Если поле присутствует в секции ВЫБРАТЬ временной таблицы, но напрямую не выводится в финальном отчете и не участвует в расчете ресурсов, СКД в целях оптимизации может просто исключить его из выборки. Это кардинально меняет группировку данных в СУБД.
Проанализируем ситуацию: если у нас есть временная таблица с группировкой по номенклатуре и складу, а в итоговом отчете склад не выводится, СКД может автоматически убрать склад из промежуточных запросов. В результате СУБД вернет свернутые данные, и логика последующих соединений в пакете нарушится.
Чтобы избежать этой оптимизации, мы можем принудительно указать СКД, какие поля нельзя исключать из промежуточных расчетов. Для этого на вкладке «Наборы данных» в таблице полей для критически важных реквизитов необходимо установить свойство «Обязательное».
В запросе автора используется виртуальная таблица РегистрНакопления.ТоварыВРознице.ОстаткиИОбороты. Расчет остатков — одна из самых сложных задач для СУБД, и СКД имеет встроенный механизм для работы с ними, но он требует правильной разметки схемы.
Если мы просто выберем поля остатков (например, КоличествоКонечныйОстаток) и выведем их в группировках отчета без настройки ролей, СКД не поймет, что это остаток. Платформа будет относиться к этому полю как к обычному обороту и просто просуммирует его по всем группировкам.
Например, если остаток товара на понедельник был 10 шт., на вторник — 10 шт., а на среду — 10 шт., то при группировке за неделю СКД без настроенных ролей выдаст итог в 30 шт. Это в корне неверно. Чтобы СКД рассчитывала остатки методом получения последнего значения на конец периода, нам необходимо настроить Роли полей:
Период или Неделя) нужно установить роль «Период» с указанием типа и иерархии периодов.Количество), тип остатка (Начальный, Конечный) и связать его с соответствующим полем периода.В исходном запросе автора мы видим огромную конструкцию из объединений чисел от 0 до 9 и их полных соединений (cross join) по условию ПО (ИСТИНА). Таким образом программист сгенерировал виртуальную таблицу дат (от 0 до 9999 дней от базовой даты 01.01.2000), чтобы получить непрерывный календарь дней в периоде отчета.
В обычных запросах это делается, чтобы заполнить «дыры»: если в какой-то день не было движений по регистру, виртуальная таблица остатков не вернет по нему запись. Левое соединение реальных остатков с таким сгенерированным календарем позволяет получить остатки на каждый день периода, даже если движений не было.
Однако в СКД ручная генерация календаря через соединения — это тяжелейший антипаттерн, который приводит к колоссальному падению производительности и ошибкам. В СКД уже встроен мощный механизм Дополнения периодов.
Если мы настраиваем роль для поля периода, мы можем в настройках группировки отчета указать тип дополнения (например, «День», «Неделя», «Месяц»). СКД сама автоматически сгенерирует недостающие даты в итоговом макете и правильно распределит по ним остатки (продублирует их на те дни, когда движений не было). При этом СУБД не будет выполнять тяжелые полные соединения таблиц, содержащих тысячи строк.
Автор пытается рассчитать средний остаток делением суммы остатков на количество дней в неделе непосредственно внутри текста запроса: ОстаткиНеделя.КоличествоОстаток / взДнейВНеделе.Дни.
Почему это плохо для СКД? Когда СКД рассчитывает итоги по группировкам (например, по иерархии номенклатуры или по складам), она использует агрегатные функции, заданные на вкладке «Ресурсы». Если мы не определили поле как ресурс с правильной формулой, СКД на уровнях итогов будет либо просто суммировать результаты деления, либо выводить пустые значения.
Правильный подход — вычислять базовые показатели (суммы остатков, количество дней) в запросе, а саму формулу среднего значения переносить на вкладку «Ресурсы» СКД. Например, выражение для ресурса СреднедневнойОстаток может выглядеть так:
Сумма(КоличествоОстаток) / Количество(Различные Период)
В этом случае СКД на любом уровне группировки (будь то детальная строка, группа номенклатуры или общий итог) рассчитает показатель математически корректно.
Рассмотрим подробнее, как полностью переписать этот функционал, сделав его быстрым, легким и на 100% совместимым с механизмами СКД.
Мы полностью отказываемся от временной таблицы Календарь и ручного расчета нарастающего итога. Вместо этого мы будем использовать стандартную виртуальную таблицу ОстаткиИОбороты с периодичностью День. Платформа сама умеет рассчитывать остаток на каждый день, когда были движения. Наш новый запрос будет выглядеть гораздо проще:
ВЫБРАТЬ
ТоварыОстатки.Период КАК Период,
ТоварыОстатки.Склад.Подразделение КАК Магазин,
ТоварыОстатки.Номенклатура КАК Номенклатура,
ТоварыОстатки.Номенклатура.Родитель КАК Группа,
ТоварыОстатки.КоличествоНачальныйОстаток КАК НачальныйОстаток,
ТоварыОстатки.КоличествоКонечныйОстаток КАК КонечныйОстаток,
ЕСТЬNULL(Продажи.КоличествоОборот, 0) КАК ПродажиШт,
ЕСТЬNULL(Продажи.СтоимостьОборот, 0) КАК ПродажиРуб
ИЗ
РегистрНакопления.ТоварыВРознице.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, День, , Склад В (&Склад)) КАК ТоварыОстатки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, День, Подразделение В (&Подразделение)) КАК Продажи
ПО ТоварыОстатки.Период = Продажи.Период
И ТоварыОстатки.Номенклатура = Продажи.Номенклатура
И ТоварыОстатки.Склад.Подразделение = Продажи.Подразделение
Перейдем на вкладку «Наборы данных» и настроим роли для правильного расчета остатков:
Период установим роль «Период». Укажем порядковый номер 1 и тип периода День.НачальныйОстаток настроим роль «Остатки», выбрав тип «Начальный» и связав его с полем Период.КонечныйОстаток настроим роль «Остатки», выбрав тип «Конечный» и также связав его с полем Период.Чтобы в отчете выводились все дни периода (даже те, в которые не было движений), настроим дополнение периодов:
Период.&НачалоПериода и &КонецПериода.Перенесем расчет флага и средних продаж на вкладку «Ресурсы» СКД, чтобы показатели рассчитывались динамически на основе агрегированных данных. Определим следующие ресурсы:
Сумма(ПродажиШт)ВычислитьВыражениеРесурса("КонечныйОстаток", "Период")Среднее(КонечныйОстаток)Выбор Когда Среднее(КонечныйОстаток) >= 1 Тогда 1 Иначе 0 КонецСумма(ПродажиШт) / (РазностьДат(&НачалоПериода, &КонецПериода, "ДЕНЬ") + 1) * 7Если вы столкнулись с расхождением данных, не нужно пытаться «слепо» переписывать код. Нам поможет специализированный инструмент — Консоль компоновки данных. Схема работы с ней выглядит следующим образом:
.xml.Здесь вы увидите чистый запрос СУБД, который платформа сгенерировала «под капотом» с учетом всех примененных отборов и группировок. Сравнив этот запрос с исходным, вы сразу увидите, какое именно поле СКД вырезала из временных таблиц и как изменились условия связей.