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

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

В этой статье мы подробно разберем, почему возникает эта ошибка, проанализируем некорректный исходный запрос и изучим профессиональные методы решения этой проблемы с использованием стандартов разработки фирмы «1С».

Анализ исходной проблемы и поиск ошибок

Рассмотрим ситуацию: у нас есть три регистра сведений (Поставка, ВыбраныйНомерКонтракта и Оплата). Нам необходимо получить сумму из регистра Поставка, номер поставки, дату, а также наименование документа из регистра Оплата. Все это должно быть отфильтровано по выбранному номеру контракта.

Автор темы попытался решить задачу следующим запросом:


ВЫБРАТЬ
    ВыбраныйНомерКонтракта.ВыбраныйНомерКонтрактаДляФорм,
    Оплата.ОплатаНомерКонтракта,
    Оплата.ОплатаНаименованиеДокумента,
    Поставка.ПоставкаНомерКонтракта,
    Поставка.ПоставкаНомерПоставки,
    Поставка.ПоставкаДата,
    СУММА(ПоставкаСумма)
ИЗ
    РегистрСведений.Поставка КАК Поставка,
    РегистрСведений.ВыбраныйНомерКонтракта КАК ВыбраныйНомерКонтракта,
    РегистрСведений.Оплата КАК Оплата
ЛЕВОЕ СОЕДИНЕНИЕ 
    РегистрСведений.ВыбраныйНомерКонтракта КАК ВыбраныйНомерКонтракта 
    ПО (ВыбраныйНомерКонтракта.ВыбраныйНомерКонтрактаДляФорм = Оплата.ОплатаНомерКонтракта)
ГДЕ
    Поставка.ПоставкаНомерКонтракта = ВыбраныйНомерКонтракта.ВыбраныйНомерКонтрактаДляФорм
И
    Оплата.ОплатаНомерКонтракта = ВыбраныйНомерКонтракта.ВыбраныйНомерКонтрактаДляФорм
СГРУППИРОВАТЬ ПО
    ВыбраныйНомерКонтракта.ВыбраныйНомерКонтрактаДляФорм,
    Оплата.ОплатаНомерКонтракта,
    Оплата.ОплатаНаименованиеДокумента,
    Поставка.ПоставкаНомерКонтракта,
    Поставка.ПоставкаНомерПоставки,
    Поставка.ПоставкаДата,
    ПоставкаСумма

В данном запросе допущено сразу несколько критических архитектурных и синтаксических ошибок, которые приводят к искажению итоговых сумм:

  1. Перекрестное соединение (Декартово произведение): В секции ИЗ таблицы перечислены через запятую. В терминах SQL это означает операцию CROSS JOIN. Платформа 1С пытается сопоставить каждую строку таблицы Поставка с каждой строкой таблиц ВыбраныйНомерКонтракта и Оплата. Это лавинообразно увеличивает количество строк в промежуточной выборке еще до применения условий фильтрации.
  2. Повторное дублирование таблиц: Таблица РегистрСведений.ВыбраныйНомерКонтракта объявлена в секции ИЗ дважды (один раз через запятую, второй — через оператор ЛЕВОЕ СОЕДИНЕНИЕ) с абсолютно одинаковым псевдонимом. Это грубейшая ошибка построения запроса, которая ломает логику связей СУБД.
  3. Связь «один ко многим»: Если по одному контракту в регистре Поставка есть одна запись, а в регистре Оплата — три записи, то при прямом соединении этих таблиц строка поставки повторится трижды (для каждого документа оплаты). В итоге функция СУММА(ПоставкаСумма) сложит одну и ту же сумму три раза.
  4. Ошибочная группировка: Поле ПоставкаСумма добавлено в секцию СГРУППИРОВАТЬ ПО. Если мы используем агрегатную функцию СУММА(ПоставкаСумма), то группировать по этому же исходному полю бессмысленно — это разделяет строки по значениям сумм и препятствует корректному сворачиванию данных.

---

Метод №1. Использование временных таблиц и пакетных запросов (Рекомендуемый стандарт)

Для того чтобы избежать дублирования строк при связях «один ко многим», фирма «1С» рекомендует использовать пакетные запросы и временные таблицы. Этот подход позволяет изолировать расчет сумм от присоединения дополнительных аналитических разрезов.

Давайте разберем этот метод по шагам:

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

Посмотрим на пример реализации такого запроса:


ВЫБРАТЬ
    ВыбраныйНомерКонтракта.ВыбраныйНомерКонтрактаДляФорм КАК НомерКонтракта
ПОМЕСТИТЬ ВТ_ФильтрКонтрактов
ИЗ
    РегистрСведений.ВыбраныйНомерКонтракта КАК ВыбраныйНомерКонтракта
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Поставка.ПоставкаНомерКонтракта КАК НомерКонтракта,
    Поставка.ПоставкаНомерПоставки КАК НомерПоставки,
    Поставка.ПоставкаДата КАК ДатаПоставки,
    СУММА(Поставка.ПоставкаСумма) КАК СуммаПоставки
ПОМЕСТИТЬ ВТ_СгруппированныеПоставки
ИЗ
    РегистрСведений.Поставка КАК Поставка
ГДЕ
    Поставка.ПоставкаНомерКонтракта В (ВЫБРАТЬ ВТ_ФильтрКонтрактов.НомерКонтракта ИЗ ВТ_ФильтрКонтрактов)
СГРУППИРОВАТЬ ПО
    Поставка.ПоставкаНомерКонтракта,
    Поставка.ПоставкаНомерПоставки,
    Поставка.ПоставкаДата
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Оплата.ОплатаНомерКонтракта КАК НомерКонтракта,
    Оплата.ОплатаНаименованиеДокумента КАК ДокументОплаты
ПОМЕСТИТЬ ВТ_Оплаты
ИЗ
    РегистрСведений.Оплата КАК Оплата
ГДЕ
    Оплата.ОплатаНомерКонтракта В (ВЫБРАТЬ ВТ_ФильтрКонтрактов.НомерКонтракта ИЗ ВТ_ФильтрКонтрактов)
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_СгруппированныеПоставки.НомерКонтракта,
    ВТ_СгруппированныеПоставки.НомерПоставки,
    ВТ_СгруппированныеПоставки.ДатаПоставки,
    ВТ_СгруппированныеПоставки.СуммаПоставки,
    ЕСТЬNULL(ВТ_Оплаты.ДокументОплаты, "Документ оплаты не найден") КАК НаименованиеДокументаОплаты
ИЗ
    ВТ_СгруппированныеПоставки КАК ВТ_СгруппированныеПоставки
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Оплаты КАК ВТ_Оплаты
    ПО ВТ_СгруппированныеПоставки.НомерКонтракта = ВТ_Оплаты.НомерКонтракта

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

---

Метод №2. Использование явных последовательных левых соединений

Если вам требуется написать простой одиночный запрос без использования временных таблиц (например, для быстрого динамического списка или несложного отчета), необходимо полностью отказаться от перечисления таблиц через запятую в секции ИЗ и использовать явные операторы ЛЕВОЕ СОЕДИНЕНИЕ.

Проанализируем структуру связей. Нам нужно сделать одну главную таблицу точкой входа, а остальные цеплять к ней последовательно. В качестве центральной таблицы выберем список контрактов, к которому будем «приклеивать» данные о поставках и оплатах.

Рассмотрим пример правильного одиночного запроса:


ВЫБРАТЬ
    Контракты.ВыбраныйНомерКонтрактаДляФорм КАК НомерКонтракта,
    Поставки.ПоставкаНомерПоставки КАК НомерПоставки,
    Поставки.ПоставкаДата КАК ДатаПоставки,
    ЕСТЬNULL(Поставки.ПоставкаСумма, 0) КАК СуммаПоставки,
    ЕСТЬNULL(Оплаты.ОплатаНаименованиеДокумента, "") КАК ДокументОплаты
ИЗ
    РегистрСведений.ВыбраныйНомерКонтракта КАК Контракты
    
    // Присоединяем поставки по номеру контракта
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Поставка КАК Поставки
    ПО Контракты.ВыбраныйНомерКонтрактаДляФорм = Поставки.ПоставкаНомерКонтракта
    
    // Присоединяем оплаты по номеру контракта
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Оплата КАК Оплаты
    ПО Контракты.ВыбраныйНомерКонтрактаДляФорм = Оплаты.ОплатаНомерКонтракта

Важное предупреждение: Данный метод (без предварительной группировки) подходит только в том случае, если вы не используете агрегатную функцию СУММА() в этом же запросе поверх соединенных таблиц. Если вам все же необходимо вывести общие итоги по суммам поставок, при наличии нескольких оплат по одному контракту строки все равно продублируются. Для получения корректного суммирования используйте исключительно Метод №1.

---

Важные правила работы со связями в запросах 1С

Чтобы раз и навсегда обезопасить свои запросы от непреднамеренного задвоения сумм и других показателей, рекомендуем придерживаться следующих правил:

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

← На главную