При разработке отчетов по регистрам накопления часто возникает задача вывести движения за период с детализацией до документа-регистратора и одновременно получить дополнительную аналитику из реквизитов регистра (например, контрагента, ответственного или комментария), а также рассчитать начальные и конечные остатки. Однако прямое соединение виртуальной таблицы остатков и оборотов с физической таблицей движений приводит к некорректным результатам: суммы остатков и оборотов искажаются или многократно завышаются. Разберем причины такого поведения платформы 1С:Предприятие и рассмотрим правильные варианты построения запросов.
Проанализируем типичную ситуацию. В регистре накопления присутствуют измерения (например, Организация, Номенклатура, ДокументПартии), реквизит Контрагент и ресурс Сумма. Разработчик обращается к виртуальной таблице ОстаткиИОбороты с периодичностью Регистратор и выполняет левое соединение с основной таблицей регистра по полю Регистратор, чтобы получить реквизит:
ВЫБРАТЬ
ПартииОстаткиИОбороты.Организация КАК Организация,
ПартииОстаткиИОбороты.ДокументПартии КАК ДокументПартии,
ПартииОстаткиИОбороты.Регистратор КАК Регистратор,
ПартииОстаткиИОбороты.Номенклатура КАК Номенклатура,
Партии.Контрагент КАК Контрагент,
ПартииОстаткиИОбороты.СуммаНачальныйОстаток КАК СуммаНачальныйОстаток,
ПартииОстаткиИОбороты.СуммаПриход КАК СуммаПриход,
ПартииОстаткиИОбороты.СуммаРасход КАК СуммаРасход,
ПартииОстаткиИОбороты.СуммаКонечныйОстаток КАК СуммаКонечныйОстаток
ИЗ
РегистрНакопления.Партии.ОстаткиИОбороты(&НачПериода, &КонПериода, Регистратор, ДвиженияИГраницыПериода, ) КАК ПартииОстаткиИОбороты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Партии КАК Партии
ПО ПартииОстаткиИОбороты.Регистратор = Партии.Регистратор
При выполнении такого запроса суммы оборотов и остатков перестают сходиться с реальными итогами. Выясним причину этой ошибки:
ПартииОстаткиИОбороты.Регистратор = Партии.Регистратор каждая строка виртуальной таблицы соединяется со всеми строками этого же документа из таблицы движений. Возникает эффект размножения записей (декартово произведение), и числовые поля многократно увеличиваются.Контрагент должно быть измерением регистра.Регистратор виртуальная таблица формирует начальные и конечные строки периода, в которых поле Регистратор равно NULL. При соединении такие строки не находят соответствия в таблице движений.СУММА(СуммаНачальныйОстаток) по группировкам с регистратором складывает моментальные остатки на разные точки времени, что дает некорректную величину.Если в рамках одного документа-регистратора значение реквизита всегда одинаково (или нас интересует уникальный список контрагентов по документу), можно соединить виртуальную таблицу с подзапросом, содержащим ключевое слово РАЗЛИЧНЫЕ:
ВЫБРАТЬ
ПартииОстаткиИОбороты.Организация КАК Организация,
ПартииОстаткиИОбороты.ПриказМСП КАК ПриказМСП,
ПартииОстаткиИОбороты.ДокументПартии КАК ДокументПартии,
ПартииОстаткиИОбороты.Регистратор КАК Регистратор,
ПартииОстаткиИОбороты.Номенклатура КАК Номенклатура,
Партии.Контрагент КАК Контрагент,
ПартииОстаткиИОбороты.СуммаНачальныйОстаток КАК СуммаНачальныйОстаток,
ПартииОстаткиИОбороты.СуммаПриход КАК СуммаПриход,
ПартииОстаткиИОбороты.СуммаРасход КАК СуммаРасход,
ПартииОстаткиИОбороты.СуммаКонечныйОстаток КАК СуммаКонечныйОстаток
ИЗ
РегистрНакопления.Партии.ОстаткиИОбороты(
&НачПериода,
&КонПериода,
Регистратор,
ДвиженияИГраницыПериода,
ПриказМСП = &ПриказМСП
) КАК ПартииОстаткиИОбороты
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПартииВнутр.Регистратор КАК Регистратор,
ПартииВнутр.Контрагент КАК Контрагент
ИЗ
РегистрНакопления.Партии КАК ПартииВнутр
) КАК Партии
ПО ПартииОстаткиИОбороты.Регистратор = Партии.Регистратор
Важное замечание: Обратим внимание на перенос фильтра ПриказМСП = &ПриказМСП из секции ГДЕ в параметры виртуальной таблицы. Фильтрация в параметрах позволяет СУБД отсечь лишние записи на этапе чтения итогов, что существенно ускоряет выполнение запроса.
Если один документ может содержать разные значения реквизита в разных строках, использование РАЗЛИЧНЫЕ приведет к дублированию строк в случае наличия двух контрагентов в одном документе. Чтобы однозначно связать запись регистра с таблицей остатков и оборотов, установим периодичность Запись или Авто и выполним соединение по двум полям — Регистратор и НомерСтроки:
ВЫБРАТЬ
ПартииОстаткиИОбороты.Организация КАК Организация,
ПартииОстаткиИОбороты.ДокументПартии КАК ДокументПартии,
ПартииОстаткиИОбороты.Регистратор КАК Регистратор,
ПартииОстаткиИОбороты.НомерСтроки КАК НомерСтроки,
ПартииОстаткиИОбороты.Номенклатура КАК Номенклатура,
ПартииДвижения.Контрагент КАК Контрагент,
ПартииОстаткиИОбороты.СуммаПриход КАК СуммаПриход,
ПартииОстаткиИОбороты.СуммаРасход КАК СуммаРасход
ИЗ
РегистрНакопления.Партии.ОстаткиИОбороты(
&НачПериода,
&КонПериода,
Авто,
Движения,
ПриказМСП = &ПриказМСП
) КАК ПартииОстаткиИОбороты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Партии КАК ПартииДвижения
ПО ПартииОстаткиИОбороты.Регистратор = ПартииДвижения.Регистратор
И ПартииОстаткиИОбороты.НомерСтроки = ПартииДвижения.НомерСтроки
Такой подход гарантирует строгую связь 1:1 между строкой движения и строкой виртуальной таблицы оборотов, исключая задвоение числовых показателей.
Если отчет строится на базе СКД, ручной расчет остатков и обход дерева запроса через встроенный язык не требуются. В СКД проблема суммирования остатков по документам решается автоматически:
Начальный остаток и Конечный остаток с указанием соответствующего поля периода (регистратора).Сумма(СуммаНачальныйОстаток).Таким образом, при работе с реквизитами регистров накопления всегда следует учитывать связь между движениями и итогами, передавать отборы в параметры виртуальных таблиц и избегать неоднозначных соединений по неключевым полям.