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