Как получить агрегированные остатки регистра накопления и дополнительные поля без потери детализации?

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

При работе с системой 1С и построении отчетов на базе регистров накопления, особенно при использовании виртуальной таблицы Остатки(), разработчики часто сталкиваются с ситуацией, когда запрос, предназначенный для получения общих агрегированных итогов, внезапно начинает возвращать детализированные данные. Это происходит, когда в выборку, помимо измерений, по которым требуется агрегация, включаются дополнительные "информационные" поля, которые не должны влиять на группировку и суммирование.

Наш читатель столкнулся именно с такой проблемой. Он пытается получить остатки по расчетам, агрегированные по ОбъектУчета и Услуга. Однако, когда в запрос добавляется поле ПериодРасчетов (которое не является параметром виртуальной таблицы, а скорее измерением или реквизитом регистра), результат запроса перестает быть агрегированным и выводит множество строк, даже для тех записей, которые по логике должны быть свернуты в одну.

Анализируем запрос и причины проблемы

Рассмотрим исходный запрос, который вызвал затруднения:


ВЫБРАТЬ
    РасчетыОстатки.ОбъектУчета КАК ОбъектУчета,
    РасчетыОстатки.Услуга КАК Услуга,
    РасчетыОстатки.Поставщик КАК Поставщик,
    РасчетыОстатки.ОбъектРасчетов КАК ОбъектРасчетов,
    //РасчетыОстатки.ПериодРасчетов КАК ПериодРасчетов, // Проблема возникает, когда это поле активно
    РасчетыОстатки.СуммаОстаток КАК СуммаОстаток
ИЗ
    РегистрНакопления.Расчеты.Остатки(&Период, ) КАК РасчетыОстатки
ГДЕ
    РасчетыОстатки.ОбъектУчета = &ОбъектУчета
    И РасчетыОстатки.СуммаОстаток > 0

Когда поле РасчетыОстатки.ПериодРасчетов закомментировано, запрос работает как ожидалось, возвращая агрегированные остатки. Однако, если его раскомментировать, система выдает детализированные записи, включая те, которые уже должны быть "закрыты" или свернуты.

Почему так происходит? Ключевая особенность работы виртуальной таблицы Остатки() регистра накопления в 1С заключается в том, что она возвращает итоговые значения ресурсов (в данном случае СуммаОстаток) в разрезе всех измерений, которые присутствуют в запросе в списке ВЫБРАТЬ. Если вы включаете в выборку поле ПериодРасчетов (которое является измерением регистра или его реквизитом, по которому автоматически происходит детализация), система начинает рассчитывать остатки в разрезе каждой уникальной комбинации всех выбранных полей, включая ПериодРасчетов. Таким образом, даже если по основным измерениям (ОбъектУчета, Услуга) остаток уже равен нулю или его нужно было агрегировать, наличие дополнительного поля, по которому есть разные значения, приводит к развертыванию данных и появлению множества строк.

Иными словами, виртуальная таблица Остатки() ведет себя так, будто вы неявно добавили все выбранные измерения в секцию ГРУППИРОВАТЬ ПО. Если ПериодРасчетов имеет разные значения для одного и того же ОбъектУчета и Услуга, то СуммаОстаток будет рассчитана для каждой такой комбинации, а не для их общего итога.

Ожидаемый результат

Автор темы ожидает получить результат, аналогичный следующему:


Организация      Услуга          Поставщик       ОбъектРасчетов                      СуммаОстаток ПериодРасчетов
ООО "Кварта"     Электроэнергия  ООО "ТСН"       Поступление услуг 009 от 04.12.2021 425          31.12.2021

Здесь важно, что для комбинации "ООО 'Кварта' Электроэнергия" выводится только одна строка, представляющая общий остаток, и при этом доступны поля Поставщик, ОбъектРасчетов и ПериодРасчетов, причем ПериодРасчетов должен быть "последним" или "актуальным" для этого агрегированного остатка.

Подход к решению: Использование временных таблиц

Наиболее эффективным и гибким решением для подобных задач в 1С является использование временных таблиц. Этот подход позволяет разбить сложный запрос на несколько логических шагов, каждый из которых решает свою часть задачи: сначала получаем корректные агрегированные остатки, затем определяем, какие "информационные" поля должны им соответствовать, и в конце объединяем эти данные. Рассмотрим этот процесс пошагово.

Шаг 1: Получаем агрегированные остатки по основным измерениям

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


// Запрос для получения общих остатков по ОбъектуУчета и Услуге
ВЫБРАТЬ
    РасчетыОстатки.ОбъектУчета КАК ОбъектУчета,
    РасчетыОстатки.Услуга КАК Услуга,
    СУММА(РасчетыОстатки.СуммаОстаток) КАК СуммаОстаток
ПОМЕСТИТЬ ВТ_АгрегированныеОстатки
ИЗ
    РегистрНакопления.Расчеты.Остатки(
        &Период, 
        ОбъектУчета = &ОбъектУчета И СуммаОстаток > 0
    ) КАК РасчетыОстатки
СГРУППИРОВАТЬ ПО
    РасчетыОстатки.ОбъектУчета,
    РасчетыОстатки.Услуга
;

В этом запросе мы явно указываем, по каким полям нужно сгруппировать данные (ОбъектУчета, Услуга) и суммируем СуммаОстаток. В результате получаем таблицу с корректными агрегированными остатками по каждой уникальной паре "объект учета - услуга". Поля Поставщик, ОбъектРасчетов и ПериодРасчетов на этом этапе не включаются, чтобы не влиять на агрегацию.

Шаг 2: Определяем информационные поля для каждой агрегированной строки

Теперь, когда у нас есть корректные агрегированные суммы, нам нужно получить "информационные" поля (Поставщик, ОбъектРасчетов, ПериодРасчетов), которые соответствуют этим суммам. Автор темы указал, что ему нужна одна строка с остатком и последний ПериодРасчетов и связанные с ним данные. Для этого мы сначала получим все детальные остатки, которые удовлетворяют нашим условиям, а затем из них выберем максимальный ПериодРасчетов для каждой комбинации ОбъектУчета и Услуга, а также соответствующие ему Поставщика и ОбъектРасчетов.


// Запрос для сбора всех детальных остатков, из которых будем выбирать информационные поля
ВЫБРАТЬ РАЗЛИЧНЫЕ
    РасчетыОстатки.ОбъектУчета КАК ОбъектУчета,
    РасчетыОстатки.Услуга КАК Услуга,
    РасчетыОстатки.ПериодРасчетов КАК ПериодРасчетов,
    РасчетыОстатки.Поставщик КАК Поставщик,
    РасчетыОстатки.ОбъектРасчетов КАК ОбъектРасчетов
ПОМЕСТИТЬ ВТ_ДеталиОстатковДляПоискаИнформации
ИЗ
    РегистрНакопления.Расчеты.Остатки(
        &Период, 
        ОбъектУчета = &ОбъектУчета И СуммаОстаток > 0
    ) КАК РасчетыОстатки
;

// Запрос для нахождения максимального ПериодаРасчетов и получения соответствующих информационных полей
// для каждой группы ОбъектУчета и Услуга
ВЫБРАТЬ
    ДеталиОстатков.ОбъектУчета,
    ДеталиОстатков.Услуга,
    МАКСИМУМ(ДеталиОстатков.ПериодРасчетов) КАК ПериодРасчетов,
    МАКСИМУМ(ДеталиОстатков.Поставщик) КАК Поставщик,
    МАКСИМУМ(ДеталиОстатков.ОбъектРасчетов) КАК ОбъектРасчетов
ПОМЕСТИТЬ ВТ_ИнформационныеПоля
ИЗ
    ВТ_ДеталиОстатковДляПоискаИнформации КАК ДеталиОстатков
ГРУППИРОВАТЬ ПО
    ДеталиОстатков.ОбъектУчета,
    ДеталиОстатков.Услуга
;

В этом блоке мы сначала помещаем все релевантные детальные записи во временную таблицу ВТ_ДеталиОстатковДляПоискаИнформации. Затем из этой таблицы, сгруппировав по ОбъектУчета и Услуга, мы выбираем МАКСИМУМ(ПериодРасчетов). Для полей Поставщик и ОбъектРасчетов также используется функция МАКСИМУМ. Это позволяет получить одно значение для каждого из этих полей в рамках сгруппированной строки.

Важное примечание: Использование функции МАКСИМУМ для полей Поставщик и ОбъектРасчетов в этом контексте гарантирует, что мы получим одно значение для каждой агрегированной строки. Однако, это значение не всегда будет принадлежать точно той же записи, что и МАКСИМУМ(ПериодРасчетов), если существует несколько записей с одинаковым максимальным ПериодРасчетов, но разными Поставщиком или ОбъектомРасчетов. Если требуется абсолютно точное соответствие (например, чтобы Поставщик и ОбъектРасчетов были гарантированно взяты из той же строки, что и МАКСИМУМ(ПериодРасчетов)), необходимо использовать более сложную логику, например, через подзапросы с условием по всем полям или функции ранжирования (если платформа 1С позволяет, например, через использование пакетных запросов с нумерацией строк по условию, что чаще встречается в сложных отчетах).

Шаг 3: Объединяем агрегированные остатки с информационными полями

На заключительном этапе мы объединяем результаты первой и второй временных таблиц, чтобы получить желаемую структуру отчета: агрегированные остатки по основным измерениям с прикрепленными к ним информационными полями.


// Итоговый запрос: объединение агрегированных остатков с информационными полями
ВЫБРАТЬ
    АгрегированныеОстатки.ОбъектУчета,
    АгрегированныеОстатки.Услуга,
    ИнформационныеПоля.Поставщик,
    ИнформационныеПоля.ОбъектРасчетов,
    АгрегированныеОстатки.СуммаОстаток,
    ИнформационныеПоля.ПериодРасчетов
ИЗ
    ВТ_АгрегированныеОстатки КАК АгрегированныеОстатки
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ИнформационныеПоля КАК ИнформационныеПоля
    ПО АгрегированныеОстатки.ОбъектУчета = ИнформационныеПоля.ОбъектУчета
    И АгрегированныеОстатки.Услуга = ИнформационныеПоля.Услуга
;

Этот финальный запрос соединяет две временные таблицы по общим измерениям (ОбъектУчета и Услуга). В результате мы получаем таблицу, где для каждой уникальной комбинации объекта учета и услуги есть агрегированная СуммаОстаток, а также соответствующие информационные поля Поставщик, ОбъектРасчетов и ПериодРасчетов (взятые по принципу максимального периода, как мы определили на предыдущем шаге).

Альтернативные подходы и важные замечания

Хотя подход с временными таблицами является наиболее универсальным и рекомендуемым, существуют и другие возможности, о которых полезно знать:

  1. Виртуальная таблица ОстаткиИОбороты(): Эта таблица позволяет получать как остатки, так и обороты за период с разбивкой по дополнительной периодичности. Если вам нужны остатки на каждую дату ПериодРасчетов или на другую периодичность (например, по дням, месяцам), и при этом требуется увидеть начальный остаток или обороты, то ОстаткиИОбороты() может быть полезна. Однако, если задача сводится к получению единого агрегированного остатка и одной "крайней" даты, ее использование может усложнить запрос и быть избыточным.
  2. Агрегатные функции совместно с ГРУППИРОВАТЬ ПО напрямую к Остатки(): Теоретически можно попытаться использовать МАКСИМУМ(ПериодРасчетов) и СУММА(СуммаОстаток) в одном запросе с ГРУППИРОВАТЬ ПО ОбъектУчета, Услуга. Однако, как мы уже выяснили, виртуальная таблица Остатки() ведет себя специфически: она возвращает остатки в разрезе *всех* выбранных полей. Поэтому простое добавление агрегатных функций к ресурсам и группировка по измерениям не даст ожидаемого результата, если в списке ВЫБРАТЬ присутствуют другие детальные поля. Этот подход чаще работает для регистров сведений, где каждое поле является измерением, и агрегация применяется к ресурсам, а не к уже агрегированным данным.

Основные выводы и советы:

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

← На главную