При разработке отчетов в 1С программисты часто сталкиваются с классической проблемой: при соединении таблицы документов (например, «Реализация товаров и услуг») с таблицей оплат по договору, суммы оплат начинают дублироваться для каждой строки реализации. Это происходит из-за природы ЛЕВОГО СОЕДИНЕНИЯ, когда одной записи из левой таблицы (договор) соответствует несколько записей из правой (реализации). В результате общая сумма оплат в отчете многократно завышается.
Разберем подробно несколько эффективных способов решения этой задачи: от использования итогов в тексте запроса до продвинутых механизмов Системы компоновки данных (СКД) и функций Библиотеки стандартных подсистем (БСП).
Проанализируем ситуацию на примере. Допустим, у нас есть один договор, по которому прошла оплата в 800 рублей. По этому же договору оформлено три реализации. Если мы выполним простое соединение таблиц в запросе, мы получим следующую картину:
С точки зрения СУБД это корректный результат соединения, но для пользователя сумма оплаты в 2400 рублей (800 * 3) является ошибкой. Наша задача — сделать так, чтобы сумма 800 учитывалась только один раз или распределялась между документами.
Самый простой способ на уровне чистого запроса — использование механизма итогов. Мы можем сгруппировать данные по договору и вычислить максимальное значение суммы оплаты. Это не уберет дубли из детальных записей, но позволит получить корректный общий итог в иерархической выборке.
Рассмотрим пример кода:
ВЫБРАТЬ
Выручка.Регистратор КАК Реализация,
Выручка.Договор КАК Договор,
Оплата.СуммаОплаты КАК СуммаОплаты
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК Выручка
ЛЕВОЕ СОЕДИНЕНИЕ Оплата КАК Оплата
ПО Выручка.Договор = Оплата.Договор
ГДЕ
Выручка.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
ИТОГИ
МАКСИМУМ(СуммаОплаты)
ПО
Договор
При обходе такого запроса через Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам), в строке группировки «Договор» мы получим верную сумму (800), а внутри по детальным записям (реализациям) значения будут повторяться. Этот метод хорош для простых отчетов, выводимых кодом.
Если мы используем Систему компоновки данных, проблему можно решить более элегантно через настройку ресурсов. Посмотрим, как это сделать без изменения текста запроса. Для изучения вычислений и сложных механизмов в отчетах можно использовать примеры программной работы с СКД.
Разберем по шагам настройку в СКД:
СуммаОплаты в колонке Роль можно ничего не менять, но важно правильно настроить закладку Ресурсы.СуммаОплаты вместо стандартного Сумма(СуммаОплаты) напишем: Максимум(СуммаОплаты).Договор.Однако, если мы хотим получить корректную сумму по всем договорам (общий итог), просто Максимум не поможет. В этом случае используем более сложное выражение:
Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(СуммаОплаты)", "Договор"))
Это выражение сначала вычислит максимум оплаты для каждого договора, поместит их в массив, а затем просуммирует элементы массива. Так мы получим правильный итог даже при наличии дублей в детальных записях.
Проанализируем ситуацию: дубли возникают из-за соединения в одном запросе. Но СКД позволяет соединять данные на своем уровне. Это наиболее «архитектурно правильный» метод для 1С.
Рассмотрим алгоритм:
Договор (Набор 1 — источник, Набор 2 — приемник).В этом случае СКД выполнит два отдельных запроса. Сумма оплаты будет получена один раз для каждого договора и «пристыкована» к выводу, не размножаясь физически в строках табличного документа.
Если ваша база работает на современной версии платформы, мы можем использовать оконные функции прямо в запросе. Это позволяет вывести сумму только в первой строке каждой группы (например, первой реализации по договору), а в остальных вывести 0.
Посмотрим на пример логики:
ВЫБРАТЬ
Выручка.Регистратор,
Выручка.Договор,
ВЫБОР
КОГДА НОМЕР_СТРОКИ() OVER (ПАРТИЦИЯ ПО Выручка.Договор ПОРЯДОК ПО Выручка.Регистратор.Дата) = 1
ТОГДА Оплата.СуммаОплаты
ИНАЧЕ 0
КОНЕЦ КАК СуммаОплатыВыверенная
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж КАК Выручка
...
Примечание: Использование ROW_NUMBER() или НОМЕР_СТРОКИ() позволяет однозначно определить первую запись в группе и «обнулить» последующие дубликаты.
Иногда задача стоит иначе: нужно не убрать дубли, а «размазать» оплату пропорционально сумме отгрузки. Например, если оплата 800 руб., а реализации на 500, 300 и 200 руб., нужно распределить 800 руб. между ними.
Для этого удобно использовать функцию из Библиотеки стандартных подсистем (БСП). Выясним, как применить метод РаспределитьСуммуПропорциональноКоэффициентам. Для работы с процедурами и функциями подсистемы пригодится справочник по методам БСП с примерами использования.
Пример программной обработки таблицы значений после выполнения запроса:
// Подготовим массив коэффициентов (суммы реализаций)
Коэффициенты = ТЗ_Реализаций.ВыгрузитьКолонку("СуммаРеализации");
СуммаОплаты = 800;
// Распределяем сумму
РаспределеннаяОплата = ОбщегоНазначенияКлиентСервер.РаспределитьСуммуПропорциональноКоэффициентам(
СуммаОплаты,
Коэффициенты,
2
);
// Заполняем результат обратно в таблицу
Для Индекс = 0 По ТЗ_Реализаций.Количество() - 1 Цикл
ТЗ_Реализаций[Индекс].СуммаОплаты = РаспределеннаяОплата[Индекс];
КонецЦикла;
Этот метод гарантирует, что итоговая сумма всех оплат в отчете будет в точности равна сумме из документа оплаты, без погрешностей округления и лишних дублей.
Выбор метода зависит от ваших целей: