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