Почему в СрезПоследних на дату попадают суммы другого дня и как это исправить?

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

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

Выясняем причину: как работает виртуальная таблица СрезПоследних

Для начала проанализируем поведение виртуальной таблицы СрезПоследних. Главная задача этой таблицы — вернуть последнее актуальное состояние (значение ресурсов) для каждого уникального набора измерений на указанную в параметрах дату (включительно).

Представим структуру регистра сведений из примера автора темы:

Предположим, что в базе данных у нас хранятся следующие физические записи по организации "Наша Фирма":

  1. Запись от 25.02.2019: Банк "Альфа", ВидСчёта "Расчетный" — Сумма: 100 000 руб.
  2. Запись от 27.02.2019: Банк "Бета", ВидСчёта "Депозитный" — Сумма: 500 000 руб.

После этих дат никаких новых движений по регистру не производилось. Теперь мы хотим получить остатки по счетам на дату 01.03.2019. Мы передаем эту дату в параметры виртуальной таблицы:


РегистрСведений.ОстаткиПоСчетам.СрезПоследних(&ДатаКонца, Организация = &Организация)

Давайте посмотрим, что делает платформа 1С в этот момент для каждого уникального набора измерений:

Таким образом, в результат среза на 01.03.2019 попадают данные от 25.02.2019 и 27.02.2019. И это абсолютно правильное поведение системы: она показывает актуальные остатки на указанную вами дату.

В чем заключается ошибка в запросе автора?

Если логика работы среза верна, то почему автор темы видит в отчете строки, разбитые по "чужим" датам? Давайте проанализируем исходный запрос:


ВЫБРАТЬ
    ОстаткиПоСчетамСрезПоследних.Период КАК Период,
    ОстаткиПоСчетамСрезПоследних.Организация КАК Организация,
    СУММА(ОстаткиПоСчетамСрезПоследних.Сумма) КАК Сумма
ИЗ
    РегистрСведений.ОстаткиПоСчетам.СрезПоследних(&ДатаКонца, Организация = &Организация) КАК ОстаткиПоСчетамСрезПоследних
СГРУППИРОВАТЬ ПО
    ОстаткиПоСчетамСрезПоследних.Период,
    ОстаткиПоСчетамСрезПоследних.Организация

Основная ошибка здесь кроется в выборе поля Период в секции ВЫБРАТЬ и последующей группировке по нему. В виртуальной таблице СрезПоследних поле Период содержит фактическую дату совершения записи, а вовсе не ту дату, которую вы передали в параметр &ДатаКонца.

Поскольку фактические даты записей у разных банков отличаются (25.02 и 27.02), группировка по полю Период разделяет наш результат на несколько строк. В итоге мы получаем отчет вида:

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

Как исправить запрос

Если наша задача — получить консолидированную сумму остатка по организации на конкретную дату &ДатаКонца, то поле Период (как реквизит таблицы) нам вообще не нужно в выборке. Мы должны полностью исключить его из полей запроса и группировки. Если же нам обязательно нужно вывести в отчете дату, на которую получены остатки, мы можем просто вернуть значение самого параметра &ДатаКонца.

Рассмотрим правильный вариант написания запроса:


ВЫБРАТЬ
    &ДатаКонца КАК Период,
    ОстаткиПоСчетамСрезПоследних.Организация КАК Организация,
    СУММА(ОстаткиПоСчетамСрезПоследних.Сумма) КАК Сумма
ИЗ
    РегистрСведений.ОстаткиПоСчетам.СрезПоследних(&ДатаКонца, Организация = &Организация) КАК ОстаткиПоСчетамСрезПоследних
СГРУППИРОВАТЬ ПО
    ОстаткиПоСчетамСрезПоследних.Организация

При таком подходе запрос вернет одну красивую строку с общим остатком на указанную дату:

Архитектурная ошибка: почему нельзя хранить остатки в Регистре Сведений

Участники обсуждения на форуме не зря обратили внимание на само название регистра: ОстаткиПоСчетам, реализованного как Регистр Сведений (РС). Использование РС для хранения остатков и балансовых показателей — это классический антипаттерн разработки в среде 1С:Предприятие.

Давайте разберем подробнее, почему для этих целей необходимо использовать исключительно Регистры Накопления с видом "Остатки":

  1. Производительность и итоги. Регистры накопления оптимизированы для расчета остатков на любую секунду времени. Платформа 1С автоматически рассчитывает и хранит промежуточные итоги в СУБД на конец каждого месяца. Запрос остатка из регистра накопления выполняется мгновенно, так как СУБД берет сохраненную точку итогов и прибавляет/вычитает движения лишь за небольшой промежуток времени. В случае с Регистром Сведений СУБД вынуждена выполнять тяжелый подзапрос с поиском максимального периода по всей физической таблице (что на СУБД вроде PostgreSQL может вызывать серьезные просадки производительности при росте базы).
  2. Блокировки данных. При проведении документов и записи движений в регистры накопления платформа применяет управляемые блокировки только по конкретным комбинациям измерений (записи разделяются эффективно). Попытка организовать многопоточную запись и расчет остатков в РС при высокой интенсивности приведет к частым взаимным блокировкам (deadlocks).
  3. Удобство разработки. В регистре накопления вам не нужно вручную рассчитывать новое сальдо перед каждой записью. Вы просто пишете движения вида "+" (Приход) или "-" (Расход), а платформа сама берет на себя всю математику расчета конечного значения. В случае с РС вам придется перед каждой записью считывать предыдущее значение, прибавлять/вычитать сумму и перезаписывать ресурс, что порождает избыточный код и уязвимости в транзакциях.

Полезно знать: Как получить срез последних на каждую дату периода

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

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

Давайте разберем по шагам классический и наиболее производительный алгоритм решения этой задачи через пакетный запрос:

Шаг 1. Подготовка сетки дат. Нам необходимо получить временную таблицу, содержащую уникальный список дат, на которые мы хотим рассчитать срез последних.

// Предположим, ВТ_Даты уже создана и содержит колонку "Дата"

Шаг 2. Получение максимальных дат движений. Мы связываем нашу таблицу дат с физической таблицей регистра сведений по условию "период регистра меньше или равен дате из нашей сетки". Сгруппировав результат, мы находим максимальный физический период записи для каждого измерения.


ВЫБРАТЬ
    СеткаДат.Дата КАК ПериодСреза,
    РегистрФизический.Организация КАК Организация,
    МАКСИМУМ(РегистрФизический.Период) КАК МаксПериод
ПОМЕСТИТЬ ВТ_МаксимальныеДаты
ИЗ
    ВТ_Даты КАК СеткаДат
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОстаткиПоСчетам КАК РегистрФизический
        ПО РегистрФизический.Период <= СеткаДат.Дата
СГРУППИРОВАТЬ ПО
    СеткаДат.Дата,
    РегистрФизический.Организация
;

Шаг 3. Получение значений ресурсов. Теперь мы берем нашу таблицу с рассчитанными максимальными датами и соединяем ее с физической таблицей регистра сведений, чтобы получить актуальные значения ресурсов на эти моменты времени.


ВЫБРАТЬ
    ТаблицаМаксДат.ПериодСреза КАК Период,
    ТаблицаМаксДат.Организация КАК Организация,
    РегистрФизическийЗначения.Сумма КАК Сумма
ИЗ
    ВТ_МаксимальныеДаты КАК ТаблицаМаксДат
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОстаткиПоСчетам КАК РегистрФизическийЗначения
        ПО ТаблицаМаксДат.Организация = РегистрФизическийЗначения.Организация
        И ТаблицаМаксДат.МаксПериод = РегистрФизическийЗначения.Period

Этот шаблон запроса является стандартом разработки в 1С при работе с периодическими регистрами сведений и позволяет быстро и корректно получать динамику изменений на любые произвольные даты.

← На главную