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

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

При разработке отчетов в 1С программисты часто сталкиваются с классической проблемой: при соединении таблицы документов (например, «Реализация товаров и услуг») с таблицей оплат по договору, суммы оплат начинают дублироваться для каждой строки реализации. Это происходит из-за природы ЛЕВОГО СОЕДИНЕНИЯ, когда одной записи из левой таблицы (договор) соответствует несколько записей из правой (реализации). В результате общая сумма оплат в отчете многократно завышается.

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

Причина возникновения дублей (Проблема «Один ко многим»)

Проанализируем ситуацию на примере. Допустим, у нас есть один договор, по которому прошла оплата в 800 рублей. По этому же договору оформлено три реализации. Если мы выполним простое соединение таблиц в запросе, мы получим следующую картину:

  1. Реализация 1 — Договор 1 — 800 руб.
  2. Реализация 2 — Договор 1 — 800 руб.
  3. Реализация 3 — Договор 1 — 800 руб.

С точки зрения СУБД это корректный результат соединения, но для пользователя сумма оплаты в 2400 рублей (800 * 3) является ошибкой. Наша задача — сделать так, чтобы сумма 800 учитывалась только один раз или распределялась между документами.

Способ 1. Использование предложения ИТОГИ в запросе

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

Рассмотрим пример кода:


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

При обходе такого запроса через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), в строке группировки «Договор» мы получим верную сумму (800), а внутри по детальным записям (реализациям) значения будут повторяться. Этот метод хорош для простых отчетов, выводимых кодом.

Способ 2. Использование СКД и ролей полей

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

Разберем по шагам настройку в СКД:

  1. Перейдем на закладку Наборы данных.
  2. Для поля СуммаОплаты в колонке Роль можно ничего не менять, но важно правильно настроить закладку Ресурсы.
  3. В поле выражения для ресурса СуммаОплаты вместо стандартного Сумма(СуммаОплаты) напишем: Максимум(СуммаОплаты).
  4. В колонке Рассчитывать по... укажем только группировку Договор.

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

Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(СуммаОплаты)", "Договор"))

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

Способ 3. Разделение наборов данных в СКД

Проанализируем ситуацию: дубли возникают из-за соединения в одном запросе. Но СКД позволяет соединять данные на своем уровне. Это наиболее «архитектурно правильный» метод для 1С.

Рассмотрим алгоритм:

  1. Создадим Набор данных 1 (запрос), в котором выберем только реализации по договорам.
  2. Создадим Набор данных 2 (запрос), в котором выберем оплаты по договорам.
  3. Перейдем на закладку Связи наборов данных.
  4. Свяжем наборы по полю Договор (Набор 1 — источник, Набор 2 — приемник).

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

Способ 4. Оконные функции (для платформы 8.3.20+)

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

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


ВЫБРАТЬ
    Выручка.Регистратор,
    Выручка.Договор,
    ВЫБОР
        КОГДА НОМЕР_СТРОКИ() OVER (ПАРТИЦИЯ ПО Выручка.Договор ПОРЯДОК ПО Выручка.Регистратор.Дата) = 1
            ТОГДА Оплата.СуммаОплаты
        ИНАЧЕ 0
    КОНЕЦ КАК СуммаОплатыВыверенная
ИЗ
    РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК Выручка
...

Примечание: Использование ROW_NUMBER() или НОМЕР_СТРОКИ() позволяет однозначно определить первую запись в группе и «обнулить» последующие дубликаты.

Способ 5. Пропорциональное распределение суммы (БСП)

Иногда задача стоит иначе: нужно не убрать дубли, а «размазать» оплату пропорционально сумме отгрузки. Например, если оплата 800 руб., а реализации на 500, 300 и 200 руб., нужно распределить 800 руб. между ними.

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

Пример программной обработки таблицы значений после выполнения запроса:


// Подготовим массив коэффициентов (суммы реализаций)
Коэффициенты = ТЗ_Реализаций.ВыгрузитьКолонку("СуммаРеализации");
СуммаОплаты = 800;

// Распределяем сумму
РаспределеннаяОплата = ОбщегоНазначенияКлиентСервер.РаспределитьСуммуПропорциональноКоэффициентам(
    СуммаОплаты, 
    Коэффициенты, 
    2
);

// Заполняем результат обратно в таблицу
Для Индекс = 0 По ТЗ_Реализаций.Количество() - 1 Цикл
    ТЗ_Реализаций[Индекс].СуммаОплаты = РаспределеннаяОплата[Индекс];
КонецЦикла;

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

Резюме

Выбор метода зависит от ваших целей:

  1. Нужен быстрый результат в консоли запросов? Используйте ИТОГИ.
  2. Делаете сложный отчет в СКД? Используйте связи наборов данных или выражения ресурсов с агрегатными функциями по массиву.
  3. Нужна визуальная чистота (сумма только в одной строке)? Помогут оконные функции.
  4. Требуется точный финансовый расчет? Используйте пропорциональное распределение средствами БСП.
← На главную