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