Проблема задвоения (или даже многократного умножения) сумм в отчетах, созданных на базе Системы Компоновки Данных (СКД), является одной из самых «популярных» среди разработчиков 1С. Ситуация обычно выглядит так: вы создаете запрос, соединяете виртуальную таблицу остатков с физической таблицей или другим регистром, и в итоге итоговые суммы в отчете увеличиваются в несколько раз. Давайте детально разберем, почему это происходит и как правильно проектировать отчеты, чтобы данные оставались корректными.
Разберем классический пример из практики. Автор темы столкнулся с тем, что суммы в его отчете повторяются до 7 раз. Посмотрим на фрагмент его запроса:
ВЫБРАТЬ
ВалютаОстаткиИОбороты.Период КАК Период,
ВалютаОстаткиИОбороты.СуммаКонечныйОстаток КАК СуммаКонечныйОстаток,
ВалютаКонтроль.ДатаОперации КАК ДатаОперации
ИЗ
РегистрНакопления.ВалютаКонтроль.ОстаткиИОбороты(, &КонецПериода, День, , ) КАК ВалютаОстаткиИОбороты
ЛЕВОЕ СОЕДИНЕНИЕ
РегистрНакопления.ВалютаКонтроль КАК ВалютаКонтроль
ПО
ВалютаОстаткиИОбороты.Период = ВалютаКонтроль.Period
Выясним причину проблемы: виртуальная таблица ОстаткиИОбороты с периодичностью «День» выдает одну строку на каждую дату, где были движения. Однако в физической таблице регистра ВалютаКонтроль за этот же день может быть 5, 10 или 100 записей (движений). Когда мы выполняем ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN) по полю Период, СУБД сопоставляет одну строку из левой таблицы со всеми подходящими строками из правой.
В результате, если в день было 7 операций, одна строка с остатком превратится в 7 идентичных строк в выборке. Когда СКД начнет рассчитывать ресурсы (например, СУММА(СуммаКонечныйОстаток)), она просто сложит эти 7 одинаковых значений. Именно так получается «магическое» умножение остатка на количество движений.
Самый правильный архитектурный подход в 1С — это использование механизмов самой системы компоновки вместо «сырых» соединений в тексте запроса. Давайте рассмотрим, как это сделать по шагам:
В чем преимущество? Когда СКД видит два связанных набора данных, она рассчитывает итоги по каждому набору независимо. Сумма остатка будет взята из первого набора один раз, а детальные записи из второго набора просто «присоединятся» к ней в группировке, не влияя на математический расчет ресурсов первого набора. Это исключает задвоение на уровне логики самого движка компоновки.
Если вы все же решили использовать один запрос, проанализируем ошибку в соединении периодов. В виртуальной таблице ОстаткиИОбороты при детализации «День» поле Период — это всегда 00:00:00 (начало дня). В физической же таблице Период — это точное время документа.
Соединение ВалютаОстаткиИОбороты.Период = ВалютаКонтроль.Период сработает только для тех документов, которые были проведены ровно в полночь. Все остальные записи либо вообще не попадут в соединение (вы получите NULL), либо, если записей в полночь несколько, вы получите дубли.
Чтобы соединение было корректным, необходимо привести периоды к одному виду:
ПО ВалютаОстаткиИОбороты.Период = НАЧАЛОПЕРИОДА(ВалютаКонтроль.Период, ДЕНЬ)
И ВалютаОстаткиИОбороты.Организация = ВалютаКонтроль.Организация
// И другие измерения...
Важно: Всегда соединяйте таблицы по максимально полному набору измерений (Организация, Склад, Номенклатура и т.д.). Соединение только по периоду — это прямой путь к получению «каши» из данных разных контрагентов в одной строке.
Рассмотрим ситуацию, когда нам нужно подтянуть из второй таблицы какое-то одно значение (например, дату последней операции или номер документа), но при этом нельзя допустить размножения строк. В этом случае правую таблицу нужно предварительно «схлопнуть» во вложенном запросе.
Пример структуры запроса:
ВЫБРАТЬ
Остатки.Период,
Остатки.СуммаОстаток,
Контроль.МаксДата
ИЗ
РегистрНакопления.ВалютаКонтроль.ОстаткиИОбороты(...) КАК Остатки
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(Период, ДЕНЬ) КАК ПериодДень,
МАКСИМУМ(ДатаОперации) КАК МаксДата
ИЗ
РегистрНакопления.ВалютаКонтроль
СГРУППИРОВАТЬ ПО
НАЧАЛОПЕРИОДА(Период, ДЕНЬ)
) КАК Контроль ПО Остатки.Период = Контроль.ПериодДень
Используя группировку (СГРУППИРОВАТЬ ПО) во вложенном запросе, мы гарантируем, что на одну дату из таблицы остатков придется ровно одна строка из таблицы контроля. Суммы остатков останутся в сохранности.
Посмотрим на настройки набора данных в конструкторе СКД. У каждого поля есть колонка «Роль». Для полей остатков крайне важно правильно настроить параметры:
Сумма(ВычислитьВыражениеСГруппировкойТаблицаЗначений(...)) в поле ресурса, но это считается сложным и менее производительным методом.Часто новички пытаются использовать ключевое слово РАЗЛИЧНЫЕ (DISTINCT), чтобы убрать дубли. Проанализируем, почему в 99% случаев это бесполезно для отчетов с суммами. Если во второй таблице данные хоть немного отличаются (например, разное время операции, разные комментарии или номера документов), то вся строка целиком признается уникальной. РАЗЛИЧНЫЕ не удалит эти строки, и функция СУММА() в ресурсах СКД все равно сложит их все, выдав неверный результат.
Чтобы ваш отчет в СКД не «врал» в итоговых суммах, придерживайтесь следующих правил:
Соблюдение этих принципов позволит вам создавать надежные отчеты, которые корректно отображают финансовые показатели даже при сложной структуре данных.