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

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

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

В этой статье мы подробно разберем несколько проверенных способов решения этой задачи, проанализируем их плюсы и минусы с точки зрения производительности СУБД, а также рассмотрим особенности работы в обработчике ПередЗаписью.

Постановка технической задачи

Представим сценарий: в документе фиксируется текущее показание весов (переменная ПоказаниеВесовКг). Нам необходимо рассчитать чистый вес (разницу между текущим показателем и предыдущим зафиксированным значением в периодическом регистре сведений ПоказанияВесов) по формуле:

Чистый Вес = Показание Весов (Текущее) - Показание Весов (Предыдущее)

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

---

Решение №1: Использование объекта Граница (Рекомендуемый стандарт платформы)

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

Давайте разберем, почему нельзя просто передать дату документа в срез последних. Если документ уже был записан ранее, его старые движения уже присутствуют в базе. Обычный запрос среза на дату документа вернет движения самого этого документа. Чтобы "открутить" время назад и получить данные до секунды проведения текущего документа, мы используем конструкцию ВидГраницы.Исключая.

Рассмотрим пример реализации функции получения предыдущего показания:


Функция ПолучитьПредыдущееПоказание(СсылкаНаДокумент, ДатаДокумента) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   ПоказанияВесовСрезПоследних.Показание КАК Показание
        |ИЗ
        |   РегистрСведений.ПоказанияВесов.СрезПоследних(&ГраницаВремени, ) КАК ПоказанияВесовСрезПоследних";
    
    // Если документ новый и еще не записан в базу, у него нет МоментаВремени.
    // В таком случае достаточно среза на дату документа.
    Если СсылкаНаДокумент.Пустая() Тогда
        Запрос.УстановитьПараметр("ГраницаВремени", ДатаДокумента);
    Иначе
        // Если документ существует, строим границу, исключая момент времени самого документа.
        // Это отсечет движения текущего документа, даже если они уже записаны.
        МоментДокумента = Новый МоментВремени(ДатаДокумента, СсылкаНаДокумент);
        ГраницаВремени = Новый Граница(МоментДокумента, ВидГраницы.Исключая);
        Запрос.УстановитьПараметр("ГраницаВремени", ГраницаВремени);
    КонецЕсли;
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Показание;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Затем в процедуре ПередЗаписью (или в модуле проведения документа) мы можем выполнить расчет:


Процедура РассчитатьЧистыйВес()
    
    ПредыдущееПоказание = ПолучитьПредыдущееПоказание(ЭтотОбъект.Ссылка, ЭтотОбъект.Дата);
    
    Если ПредыдущееПоказание = 0 Тогда
        ЭтотОбъект.Вес = ЭтотОбъект.ПоказаниеВесовКг;
    Иначе
        ЭтотОбъект.Вес = ЭтотОбъект.ПоказаниеВесовКг - ПредыдущееПоказание;
    КонецЕсли;
    
КонецПроцедуры

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

  1. Высокая скорость работы. Платформа 1С оптимизирует запросы к виртуальным таблицам СрезПоследних на уровне СУБД. При наличии индексов по измерениям выборка происходит мгновенно.
  2. Корректная работа при повторной записи. Сколько бы раз пользователь ни нажимал кнопку "Записать", алгоритм всегда будет возвращать корректное предыдущее значение, игнорируя текущую транзакцию документа.

---

Решение №2: Выборка по физической таблице с сортировкой (Альтернативный вариант)

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

Рассмотрим, как выглядит такой запрос:


Функция ПолучитьПредыдущееПоказаниеЧерезФизическуюТаблицу(СсылкаНаДокумент, ДатаДокумента) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |   ПоказанияВесов.Показание КАК Показание
        |ИЗ
        |   РегистрСведений.ПоказанияВесов КАК ПоказанияВесов
        |ГДЕ
        |   ПоказанияВесов.Период < &ДатаДокумента
        |   И ПоказанияВесов.Регистратор <> &ТекущийДокумент
        |УПОРЯДОЧИТЬ ПО
        |   Период УБЫВ";
    
    Запрос.УстановитьПараметр("ДатаДокумента", ДатаДокумента);
    Запрос.УстановитьПараметр("ТекущийДокумент", СсылкаНаДокумент);
    
    РезультатЗапроса = Запрос.Выполнить();
    Выборка = РезультатЗапроса.Выбрать();
    
    Если Выборка.Следующий() Тогда
        Возврат Выборка.Показание;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Важные нюансы данного метода:

Ограничение метода: Если в одну и ту же секунду записано несколько документов от разных регистраторов, простое сравнение периодов по знаку "меньше" (<) может пропустить документы, созданные в ту же секунду. В высоконагруженных системах для точного позиционирования всегда следует использовать МоментВремени.

---

Решение №3: Двухэтапный запрос через временные таблицы ("Срез предпоследних")

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

Суть метода заключается в следующем:

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

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


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

Запрос.УстановитьПараметр("ПериодСреза", ДатаДокумента);
РезультатЗапроса = Запрос.Выполнить();

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

---

Какое решение выбрать? Резюме разработчика

Для большинства повседневных задач автоматизации в 1С (расчет веса, получение предыдущей цены номенклатуры при проведении накладной, определение предыдущего статуса заказа) идеальным выбором будет Решение №1 (с использованием объекта Граница). Оно является наиболее "чистым" с точки зрения стандартов разработки 1С, так как платформа берет на себя все тонкости транзакционных блокировок и особенности СУБД.

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

← На главную