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