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