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