При разработке отчетов в 1С с использованием Системы Компоновки Данных (СКД) задача сравнения показателей за два разных периода является одной из самых частых. Нередко возникает ситуация: мы пишем запрос, проверяем его в консоли запросов — все работает идеально, данные выводятся за оба периода. Но как только мы переносим этот запрос в СКД, данные за второй период пропадают, задваиваются или заполняются значениями из первого периода. Разберем по шагам, почему так происходит и как правильно решить эту задачу.
Главная причина такого поведения кроется во встроенном макро-оптимизаторе Системы Компоновки Данных. В отличие от обычной консоли, которая выполняет запрос ровно в том виде, в котором он написан, СКД перед отправкой запроса к базе данных модифицирует его в зависимости от текущих настроек отчета.
Если параметры виртуальной таблицы не прописаны в явном виде через расширение языка запросов СКД, платформа может переопределить их своими стандартными глобальными параметрами (например, стандартным периодом отчета). Из-за этого для обеих таблиц (и первого, и второго периода) применяются одни и те же даты, и данные выводятся некорректно.
Рассмотрим исходный запрос, в котором данные берутся из двух виртуальных таблиц и соединяются:
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.КоличествоОборот КАК Количество,
ПродажиОбороты.СуммаОборот КАК Сумма
ПОМЕСТИТЬ ПервыйПериод
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, Месяц, ) КАК ПродажиОбороты
;
// ... Аналогично для второго периода ...
Чтобы СКД поняла, что для каждой таблицы нужно использовать свои независимые параметры, необходимо заключить их в фигурные скобки. Посмотрим на правильный вариант написания:
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.КоличествоОборот КАК Количество,
ПродажиОбороты.СуммаОборот КАК Сумма,
ПродажиОбороты.Период КАК Период
ПОМЕСТИТЬ ПервыйПериод
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода {(&НачалоПериода)}, &КонецПериода {(&КонецПериода)}, Месяц, ) КАК ПродажиОбороты
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ПродажиОбороты2.Номенклатура КАК Номенклатура2,
ПродажиОбороты2.КоличествоОборот КАК Количество2,
ПродажиОбороты2.СуммаОборот КАК Сумма2,
ПродажиОбороты2.Период КАК Период2
ПОМЕСТИТЬ ВторойПериод
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода2 {(&НачалоПериода2)}, &КонецПериода2 {(&КонецПериода2)}, Месяц, ) КАК ПродажиОбороты2
Конструкция вида {(&НачалоПериода)} жестко фиксирует параметры виртуальной таблицы для компоновщика. Теперь СКД не будет подменять их общим периодом отчета.
Даже если мы исправили параметры, использование ЛЕВОЕ СОЕДИНЕНИЕ для сравнения периодов — это логическая ошибка. Выясним причину: если в первом периоде продаж конкретной номенклатуры не было, а во втором были, то при левом соединении эти данные просто не попадут в итоговый результат.
Замена на ПОЛНОЕ СОЕДИНЕНИЕ решает проблему потерянных записей, но объединение нескольких виртуальных таблиц через полное соединение сильно нагружает систему и снижает производительность. Разберем более оптимальные альтернативы.
Вместо соединений гораздо эффективнее использовать конструкцию ОБЪЕДИНИТЬ ВСЕ с последующим группированием. Напишем первый запрос к оборотам за первый период, где поля для второго периода заполним нулями. Затем добавим второй запрос, где нулями заполним поля первого периода.
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.КоличествоОборот КАК Количество1,
ПродажиОбороты.СуммаОборот КАК Сумма1,
0 КАК Количество2,
0 КАК Сумма2
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода {(&НачалоПериода)}, &КонецПериода {(&КонецПериода)}, , ) КАК ПродажиОбороты
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ПродажиОбороты2.Номенклатура,
0,
0,
ПродажиОбороты2.КоличествоОборот,
ПродажиОбороты2.СуммаОборот
ИЗ
РегистрНакопления.Продажи.Обороты(&НачалоПериода2 {(&НачалоПериода2)}, &КонецПериода2 {(&КонецПериода2)}, , ) КАК ПродажиОбороты2
Итоговый результат СКД сама свернет по полю Номенклатура (при наличии соответствующих ресурсов). Это работает намного быстрее и гарантирует, что ни одна позиция не потеряется.
СКД позволяет вообще не писать громоздкие соединения в одном тексте запроса. Проанализируем этот метод:
Номенклатура.Система сама корректно соединит данные при выводе отчета (используя алгоритмы, аналогичные полному соединению с учетом иерархии), что делает код чище и проще для поддержки.
Самый правильный и "естественный" для архитектуры СКД путь — не хардкодить колонки в тексте запроса. Посмотрим на этот подход подробнее. Мы пишем один простейший запрос к регистру продаж за общий охватывающий период (включающий и первый, и второй интервалы времени).
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.Период КАК Период,
ПродажиОбороты.КоличествоОборот КАК Количество,
ПродажиОбороты.СуммаОборот КАК Сумма
ИЗ
РегистрНакопления.Продажи.Обороты(&ОбщееНачало, &ОбщийКонец, Месяц, ) КАК ПродажиОбороты
Далее в настройках структуры отчета СКД мы создаем таблицу. В строки помещаем группировку Номенклатура, а в колонки — Период. Ресурсы система рассчитает автоматически. Если нужно вывести разницу (отклонение) между периодами, это легко реализуется через вычисляемые поля или пользовательские поля прямо в пользовательских настройках отчета без изменения самого запроса.