Как устранить задвоение сумм в запросе 1С при соединении нескольких таблиц

Программист 1С v8.3 (Обычные формы) 1C:Бухгалтерия Бухгалтерский учет
← На главную

При разработке сложных отчетов в системе 1С:Предприятие программисты часто сталкиваются с ситуацией, когда итоговые суммы по документу (например, по счету на оплату) начинают «размножаться» — увеличиваться в несколько раз. Эта проблема типична для запросов, где к одной основной таблице присоединяются несколько зависимых таблиц с типом связи «один ко многим». Разберем причины этого явления и рассмотрим наиболее эффективные способы решения задачи.

Почему происходит задвоение (мультипликация) данных

Прежде чем переходить к коду, выясним причину ошибки. Проанализируем стандартную ситуацию: у нас есть один СчетНаОплату, к которому привязано два документа ПоступлениеТоваровУслуг и три документа ПлатежноеПоручение. Если мы попытаемся соединить эти таблицы через ЛЕВОЕ СОЕДИНЕНИЕ по полю Счет, СУБД выполнит операцию декартова произведения в рамках этого ключа. В результате вместо одной строки со счетом мы получим 2 (поступления) * 3 (оплаты) = 6 строк. При расчете итогов функция СУММА(СуммаСчета) сложит значение суммы счета шесть раз вместо одного.

Рассмотрим подробнее три основных способа решения этой проблемы: использование объединений вместо соединений, предварительную агрегацию во временных таблицах и использование механизмов СКД.

Решение 1: Метод объединения (UNION ALL)

Это наиболее гибкий способ, который позволяет собрать данные «вертикально», а не «горизонтально». Вместо того чтобы пытаться выстроить все документы в одну строку, мы выбираем их последовательно, заполняя недостающие поля значениями NULL или 0. Посмотрим на пример правильно сконструированного запроса:


ВЫБРАТЬ
    Счет.Ссылка КАК СчетНаОплату,
    Счет.СуммаДокумента КАК СуммаСчета,
    NULL КАК ДокументПоступления,
    0 КАК СуммаПоступления,
    NULL КАК ДокументОплаты,
    0 КАК СуммаОплаты
ПОМЕСТИТЬ ВТ_Данные
ИЗ
    Документ.СчетНаОплатуПоставщика КАК Счет
ГДЕ
    Счет.Дата МЕЖДУ &НачалоПериода И &КонецПериода

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ХозрасчетныйОбороты.Субконто3,
    0,
    ХозрасчетныйОбороты.Регистратор,
    ХозрасчетныйОбороты.СуммаОборот,
    NULL,
    0
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Обороты(,, Регистратор, Счет = &Счет60,,,) КАК ХозрасчетныйОбороты

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ХозрасчетныйОбороты.Субконто3,
    0,
    NULL,
    0,
    ХозрасчетныйОбороты.Регистратор,
    ХозрасчетныйОбороты.СуммаОборотДт
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Обороты(,, Регистратор, Счет = &Счет60, &КорСчет51,,) КАК ХозрасчетныйОбороты
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.СчетНаОплату,
    СУММА(ВТ.СуммаСчета) КАК СуммаСчета,
    ВТ.ДокументПоступления,
    СУММА(ВТ.СуммаПоступления) КАК СуммаПоступления,
    ВТ.ДокументОплаты,
    СУММА(ВТ.СуммаОплаты) КАК СуммаОплаты
ИЗ
    ВТ_Данные КАК ВТ
СГРУППИРОВАТЬ ПО
    ВТ.СчетНаОплату,
    ВТ.ДокументПоступления,
    ВТ.ДокументОплаты
ИТОГИ
    СУММА(СуммаСчета),
    СУММА(СуммаПоступления),
    СУММА(СуммаОплаты)
ПО
    ВТ.СчетНаОплату

Важный нюанс: при использовании ОБЪЕДИНИТЬ ВСЕ крайне важно, чтобы поля во всех частях запроса совпадали по типам и порядку. Обратите внимание, как мы «разносим» суммы по разным колонкам, чтобы при итоговом суммировании они не перемешались. Поле СуммаСчета заполнено только в первой части, а в остальных там стоит 0. Это гарантирует, что сумма счета будет посчитана ровно один раз.

Решение 2: Предварительная агрегация во временных таблицах

Если специфика отчета требует именно использования соединений (например, нужно вывести данные строго в одну строку без иерархии), мы должны предварительно «схлопнуть» данные по поступлениям и оплатам до уровня одного счета. Разберем этот алгоритм по шагам:

  1. Создаем временную таблицу ВТ_Поступления, где группируем все суммы поступлений по полю Счет. В этой таблице на каждый счет должна приходиться ровно одна строка.
  2. Создаем временную таблицу ВТ_Оплаты, аналогично группируя суммы оплат по счету.
  3. В основном запросе соединяем таблицу СчетаНаОплату с этими временными таблицами.

Пример кода для агрегации:


ВЫБРАТЬ
    Обороты.Субконто3 КАК Счет,
    СУММА(Обороты.СуммаОборот) КАК ИтогоПоступление
ПОМЕСТИТЬ ВТ_СгруппированныеПоступления
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Обороты(,, , Счет = &Счет60) КАК Обороты
СГРУППИРОВАТЬ ПО
    Обороты.Субконто3

После такой подготовки связь будет иметь тип «один к одному», и никакого умножения строк не произойдет. Однако у этого метода есть минус: вы теряете возможность вывести в этом же отчете детальные ссылки на конкретные документы поступления или оплаты (так как вы их свернули в одну сумму).

Решение 3: Система компоновки данных (СКД)

Проанализируем ситуацию с точки зрения использования современных инструментов платформы. В СКД проблема задвоения решается наиболее элегантно через использование нескольких наборов данных. Рассмотрим, как это настроить:

  1. Создаем Набор данных 1 (Запрос) для получения списка счетов и их сумм.
  2. Создаем Набор данных 2 (Запрос) для получения данных по оплатам в разрезе счетов.
  3. Создаем Набор данных 3 (Запрос) для получения данных по поступлениям.
  4. На закладке «Связи наборов данных» указываем связь между Набором 1 и Набором 2 по полю Счет, а также между Набором 1 и Набором 3.

Система компоновки данных сама понимает, что это разные потоки данных. При расчете итогов она не будет перемножать строки, а корректно выведет суммы из каждого набора данных отдельно. Это самый профессиональный подход, рекомендуемый для разработки отчетов.

Практические советы при работе с виртуальными таблицами

При работе с бухгалтерскими регистрами (таблица Хозрасчетный.Обороты) помните о следующих правилах:

Выяснив причину задвоения и выбрав подходящий метод (ОБЪЕДИНИТЬ ВСЕ для простых отчетов или несколько наборов данных в СКД для сложных), вы сможете строить точные и производительные отчеты без математических ошибок в итогах.

← На главную