Почему удваивается сумма в пакетном запросе 1С при использовании ЛЕВОГО СОЕДИНЕНИЯ?

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

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

Основной вопрос: из-за чего может происходить задвоение значения в запросе, особенно при расчете итоговых сумм после соединения таблиц?

Анализ исходного запроса и причина проблемы

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

Проблемный код:

Первый пакет, создающий временную таблицу:


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

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

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

Второй пакет, где происходит задвоение:


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

В чем заключается корень проблемы?

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

  1. Первая строка: ГлавныйДокумент1, СуммаФакт, 0
  2. Вторая строка: ГлавныйДокумент1, 0, СуммаФактЗарплата

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

В результате этого дублирования, при последующей агрегации с помощью СУММА(ВЫРАЗИТЬ(СвойстваДокументов.Значение ...)), значение из поля Значение будет просуммировано дважды, что и приводит к задвоению итоговой суммы СуммаОбращения.

Решение 1: Дополнительная группировка временной таблицы

Самый прямой способ исправить ситуацию — это "схлопнуть" записи во временной таблице так, чтобы для каждого документа осталась только одна строка. Для этого можно ввести еще один, промежуточный, пакет запроса, который сгруппирует результаты первого пакета.

Шаг 1. Оставляем первый пакет без изменений, он создает таблицу ВТ_СуммаПоНарядам.

Шаг 2. Добавляем новый пакет, который группирует данные из ВТ_СуммаПоНарядам и помещает их в новую временную таблицу, например, ВТ_СуммыПоНарядамСГруппировкой.


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

Шаг 3. В финальном пакете запроса выполняем соединение уже с новой, сгруппированной таблицей ВТ_СуммыПоНарядамСГруппировкой. Теперь связь между таблицами будет "один-к-одному", и дублирования не произойдет.


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

Этот метод абсолютно рабочий и решает проблему. Его недостаток — создание дополнительной временной таблицы, что может немного усложнить код и незначительно повлиять на производительность.

Решение 2: Использование вложенного запроса (более оптимальный вариант)

Существует более элегантный и эффективный способ. Вместо того чтобы группировать каждую часть ОБЪЕДИНИТЬ ВСЕ по отдельности, можно сначала объединить "сырые" данные, а затем применить группировку ко всему объединенному результату. Это делается с помощью вложенного запроса.

В этом случае первый пакет будет выглядеть так:


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

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

    ВЫБРАТЬ
        НарядРасходы.Ссылка.ГлавныйДокумент,
        0,
        НарядРасходы.СуммаФакт
    ИЗ
        Документ.Наряд.Расходы КАК НарядРасходы
    ГДЕ
        НарядРасходы.СтатьяБюджета = &СтатьяБюджета
        И НарядРасходы.Ссылка.Состояние = &Состояние
) КАК Табличка
СГРУППИРОВАТЬ ПО
    Табличка.ГлавныйДокумент

Как это работает?

  1. Конструкция ИЗ (...) КАК Табличка создает виртуальную таблицу "на лету".
  2. Сначала выполняются обе выборки внутри скобок и объединяются через ОБЪЕДИНИТЬ ВСЕ. Важно, что внутри вложенного запроса группировка уже не нужна. Мы получаем плоский список всех расходов.
  3. Затем внешний запрос берет эту виртуальную таблицу Табличка и применяет к ней группировку СГРУППИРОВАТЬ ПО Табличка.ГлавныйДокумент, корректно суммируя все значения.

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

Ключевые выводы и рекомендации

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

  1. Предварительная агрегация. Золотое правило: всегда агрегируйте (группируйте) данные в присоединяемых таблицах ДО того, как вы будете их соединять с основной таблицей, если в них может быть более одной записи на ключ соединения.
  2. Анализ связей. Перед написанием запроса с соединениями четко представьте себе тип связи между таблицами (один-к-одному, один-ко-многим). Именно связь "один-ко-многим" является главной причиной дублирования данных и некорректных итогов.
  3. Поэтапная отладка. Если вы столкнулись с непонятным поведением запроса, используйте Консоль запросов. Выполняйте каждый пакет по отдельности, анализируйте результат во временных таблицах. Это самый быстрый способ найти, на каком именно этапе возникают дубли или некорректные данные.
← На главную