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

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

В процессе разработки в среде 1С:Предприятие программисты часто сталкиваются с необходимостью получить не просто самое последнее актуальное состояние регистра сведений, а его предыдущее значение — так называемый «срез предпоследних». Типичный пример: перед проведением документа нам нужно узнать, какое сырье было налито в цистерну до текущей технологической операции, или какой статус у вагона был до момента текущей регистрации.

При написании таких запросов легко допустить неочевидные архитектурные ошибки. В результате запрос отлично работает на «свежих» тестовых данных текущего года, но начинает возвращать некорректную информацию при анализе исторических данных за прошлые периоды (например, когда предыдущее значение внезапно оказывается больше текущего). В этой статье мы подробно разберем, почему возникают такие ошибки, и рассмотрим несколько надежных способов получения предпоследних записей в периодическом регистре сведений.

Анализ исходной проблемы: почему «предыдущее значение больше текущего»?

Давайте проанализируем запрос, с которым столкнулся автор темы на форуме. В его коде присутствовали две критические логические ошибки, ломающие выборку данных за прошлые периоды.

Ошибка первая: Использование оператора «Не равно» (<>) вместо «Меньше» (<)

В левом соединении автор связывает срез последних с физической таблицей регистра по условию:

НаливПродуктаНовСрезПоследних.Период <> НаливПродуктаНов.Период

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

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

Автор использует следующую конструкцию получения среза:

РегистрСведений.НаливПродуктаНов.СрезПоследних(, Регистратор = &Регистратор)

Первым параметром в виртуальную таблицу СрезПоследних передается период (дата среза). Если он не указан, платформа 1С рассчитывает срез на самую актуальную дату — то есть на «сегодня» («сейчас»). Даже если во втором параметре (в отборе) указан конкретный регистратор прошлых лет, логика работы платформы следующая: сначала СУБД строит срез последних на текущую дату, а затем накладывает фильтр по вашему регистратору. Если этот регистратор старый, а по вагону за прошедшие годы уже появились новые записи, то срез последних на «сегодня» вообще не будет содержать записей с этим старым регистратором. Запрос вернет пустой результат или некорректные данные.

---

Вариант 1. Исправление исходного запроса (Классический метод)

Если мы хотим оставить логику исходного запроса автора, но сделать ее полностью рабочей и корректной, нам необходимо внести два изменения:

  1. Передать дату документа в качестве первого параметра виртуальной таблицы СрезПоследних.
  2. Заменить условие связи <> на строгое > (период среза строго больше периода физической таблицы регистра).

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


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Периоды.Вагон КАК Вагон,
    ВТ_Периоды.ПериодСреза КАК ПериодСреза,
    ВТ_Периоды.ИзПодТекущая КАК ИзПодТекущая,
    ВТ_Периоды.ПредыдущийПериод КАК ПредыдущийПериод,
    РегистрПредпоследний.ИзПод КАК ИзПодПредыдущая,
    РегистрПредпоследний.Остаток КАК ОстатокПредыдущий
ИЗ
    ВТ_Периоды КАК ВТ_Периоды
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.НаливПродуктаНов КАК РегистрПредпоследний
        ПО ВТ_Периоды.Вагон = РегистрПредпоследний.Вагон
            И ВТ_Периоды.ПредыдущийПериод = РегистрПредпоследний.Период

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

---

Вариант 2. Лучшая практика — Использование объекта «Граница» (Для документов)

Очень часто программисты усложняют себе жизнь поиском «среза предпоследних», когда реальная задача заключается в следующем: получить состояние регистра непосредственно перед проведением конкретного документа.

В платформе 1С для этих целей предусмотрен специальный инструмент — объект встроенного языка Граница. Если документ делает движения в периодическом регистре сведений, то на его МоментВремени актуальным (последним) значением в регистре будет являться то, которое записал сам этот документ. Чтобы получить состояние, которое было в системе секундой ранее (до проведения нашего документа), достаточно выполнить обычный и очень быстрый запрос СрезПоследних, передав туда в качестве периода Границу с видом Исключая.

Разберем по шагам, как это реализовать в коде программного модуля:


// 1. Создаем границу контроля, исключая момент времени текущего документа
ГраницаКонтроля = Новый Граница(ЭтотОбъект.МоментВремени(), ВидГраницы.Исключая);

// 2. Инициализируем запрос
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	СрезПоследних.Вагон КАК Вагон,
    |	СрезПоследних.ИзПод КАК ИзПодПредыдущая,
    |	СрезПоследних.Остаток КАК ОстатокПредыдущий
    |ИЗ
    |	РегистрСведений.НаливПродуктаНов.СрезПоследних(&ПериодСреза, Вагон = &Вагон) КАК СрезПоследних";

Запрос.УстановитьПараметр("ПериодСреза", ГраницаКонтроля);
Запрос.УстановитьПараметр("Вагон", СсылкаНаВагон);

РезультатЗапроса = Запрос.Выполнить();

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

---

Вариант 3. Метод «Двойного среза последних» с исключением (Для пакетных запросов)

Что делать, если нам нужно получить предпоследние данные не по одному вагону из конкретного документа, а сразу по всему списку вагонов (например, при формировании сложного отчета)? Метод с объектом Граница здесь не подойдет, так как у каждого движения своя дата. В этом случае мы можем применить метод «Двойного среза» с использованием механизма исключения.

Проанализируем этот алгоритм:

  1. Сначала мы получаем самые последние записи на нужную нам дату среза и помещаем их во временную таблицу.
  2. Затем мы делаем второй срез последних на ту же самую дату, но в параметрах отбора виртуальной таблицы исключаем пары «Измерение + Период», которые мы получили на первом шаге. Для платформы «последними» записями автоматически станут те, которые идут перед ними (то есть предпоследние).

Посмотрим на пример реализации такого запроса:


// Шаг 1: Получаем самые последние записи на дату анализа и сохраняем их период
ВЫБРАТЬ
    СрезПоследних.Вагон КАК Вагон,
    СрезПоследних.Период КАК Период
ПОМЕСТИТЬ ВТ_ПоследниеЗаписи
ИЗ
    РегистрСведений.НаливПродуктаНов.СрезПоследних(&ПериодАнализа, ) КАК СрезПоследних
ИНДЕКСИРОВАТЬ ПО
    Вагон,
    Период
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем предпоследние записи, исключив из среза последние периоды
ВЫБРАТЬ
    СрезПредпоследних.Вагон КАК Вагон,
    СрезПредпоследних.Период КАК ПредпоследнийПериод,
    СрезПредпоследних.ИзПод КАК ИзПодПредпоследний,
    СрезПредпоследних.Остаток КАК ОстатокПредпоследний
ИЗ
    РегистрСведений.НаливПродуктаНов.СрезПоследних(
        &ПериодАнализа,
        НЕ (Вагон, Период) В (
            ВЫБРАТЬ
                ВТ.Вагон,
                ВТ.Период
            ИЗ
                ВТ_ПоследниеЗаписи КАК ВТ
        )
    ) КАК СрезПредпоследних

Преимущества метода:

Таким образом, мы детально разобрали особенности работы с периодическими регистрами в 1С. В зависимости от вашей задачи — будь то проведение документа в реальном времени или формирование сложной аналитической отчетности — вы теперь можете выбрать наиболее оптимальный и защищенный от ошибок прошлых периодов метод получения данных.

← На главную