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