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