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

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

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

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

Постановка задачи и ожидаемый результат

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

  1. Для самой первой записи (Период 1) предыдущим периодом должен оставаться Период 1.
  2. Для второй записи (Период 2) предыдущим периодом становится Период 1.
  3. Для третьей записи (Период 3) — Период 2 и так далее.

Способ 1. Левое соединение с коррелированным подзапросом

Рассмотрим вариант решения задачи через прямое левое соединение таблицы регистра сведений с самой собой с отбором через условие ГДЕ. Этот способ наиболее прост для понимания логики сопоставления.

Проанализируем пример запроса на базе регистра сведений КурсыВалют:


ВЫБРАТЬ
	КурсыВалют1.Валюта КАК Валюта,
	КурсыВалют1.Период КАК Период,
	КурсыВалют1.Курс КАК Курс,
	ЕСТЬНУЛЛ(КурсыВалют2.Период, КурсыВалют1.Период) КАК ПредПериод,
	КурсыВалют2.Курс КАК ПредКурс
ИЗ
	РегистрСведений.КурсыВалют КАК КурсыВалют1
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют2
		ПО КурсыВалют1.Валюта = КурсыВалют2.Валюта
			И КурсыВалют1.Период >= КурсыВалют2.Период
ГДЕ
	КурсыВалют2.Период В
			(ВЫБРАТЬ ПЕРВЫЕ 1
				КурсыВалют3.Период КАК Период
			ИЗ
				РегистрСведений.КурсыВалют КАК КурсыВалют3
			ГДЕ
				КурсыВалют3.Валюта = КурсыВалют1.Валюта
				И КурсыВалют3.Период < КурсыВалют1.Период
			УПОРЯДОЧИТЬ ПО
				Период УБЫВ)
	ИЛИ КурсыВалют1.Период = КурсыВалют2.Период

Разберем подробно, как работает данный запрос:

  1. Мы связываем базовую таблицу КурсыВалют1 с таблицей КурсыВалют2 по совпадению измерения Валюта и условию КурсыВалют1.Период >= КурсыВалют2.Период.
  2. В секции ГДЕ мы ограничиваем выбираемый предыдущий период, запрашивая через ВЫБРАТЬ ПЕРВЫЕ 1 с сортировкой ПО Период УБЫВ самую свежую дату, которая строго меньше текущей даты.
  3. Условие ИЛИ КурсыВалют1.Период = КурсыВалют2.Период позволяет сохранить самую первую запись регистра, у которой нет более ранних дат, обеспечивая подстановку первоначального периода.
  4. Для корректной обработки значения NULL первой записи применяется функция ЕСТЬНУЛЛ(КурсыВалют2.Период, КурсыВалют1.Период).

Способ 2. Оптимальный вариант через временные таблицы и группировку

В языке запросов 1С отсутствуют оконные функции (такие как LAG или LEAD). Конструкции с подзапросами ВЫБРАТЬ ПЕРВЫЕ 1 внутри условий соединения или фильтрации ГДЕ создают коррелированные подзапросы. На больших объемах данных в СУБД это приводит к сложности порядка $O(N^2)$ и сильному падению производительности.

Чтобы избежать проблем с замедлением работы, разберем более производительный алгоритм с использованием временных таблиц и агрегатной функции МАКСИМУМ.


// Шаг 1: Помещаем исходные данные во временную таблицу
ВЫБРАТЬ
	КурсыВалют.Валюта КАК Валюта,
	КурсыВалют.Период КАК Период,
	КурсыВалют.Курс КАК Курс
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
	РегистрСведений.КурсыВалют КАК КурсыВалют
;

// Шаг 2: Находим максимальную дату, меньшую текущей
ВЫБРАТЬ
	ВТ1.Валюта КАК Валюта,
	ВТ1.Период КАК Период,
	ВТ1.Курс КАК Курс,
	МАКСИМУМ(ВТ2.Период) КАК ПредПериод
ПОМЕСТИТЬ ВТ_ПериодыСДатами
ИЗ
	ВТ_ИсходныеДанные КАК ВТ1
		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК ВТ2
		ПО ВТ1.Валюта = ВТ2.Валюта
			И ВТ1.Период > ВТ2.Период
СГРУППИРОВАТЬ ПО
	ВТ1.Валюта,
	ВТ1.Период,
	ВТ1.Курс
;

// Шаг 3: Формируем итоговый результат
ВЫБРАТЬ
	ВТ.Валюта КАК Валюта,
	ЕСТЬНУЛЛ(ВТ.ПредПериод, ВТ.Период) КАК ПредПериод,
	ВТ.Период КАК ТекущийПериод,
	ВТ.Курс КАК Курс
ИЗ
	ВТ_ПериодыСДатами КАК ВТ
УПОРЯДОЧИТЬ ПО
	ВТ.Валюта,
	ВТ.Период

Выполнение задачи через группировку по МАКСИМУМ(ВТ2.Период) дает устойчивую производительность и оптимальный план выполнения запроса на стороне СУБД.

Способ 3. Процедурный способ в коде 1С

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

Проанализируем пример процедурной обработки:


Запрос = Новый Запрос;
Запрос.Текст = 
	"ВЫБРАТЬ
	|	КурсыВалют.Валюта КАК Валюта,
	|	КурсыВалют.Период КАК Период,
	|	КурсыВалют.Курс КАК Курс
	|ИЗ
	|	РегистрСведений.КурсыВалют КАК КурсыВалют
	|УПОРЯДОЧИТЬ ПО
	|	Валюта,
	|	Период ВОЗР";

Выборка = Запрос.Выполнить().Выбрать();

ТЗРезультат = Новый ТаблицаЗначений;
ТЗРезультат.Колонки.Добавить("Валюта");
ТЗРезультат.Колонки.Добавить("ПредПериод");
ТЗРезультат.Колонки.Добавить("ТекущийПериод");
ТЗРезультат.Колонки.Добавить("Курс");

ТекущаяВалюта = Неопределено;
ПредыдущийПериод = Неопределено;

Пока Выборка.Следующий() Цикл
	Если Выборка.Валюта <> ТекущаяВалюта Тогда
		ТекущаяВалюта = Выборка.Валюта;
		ПредыдущийПериод = Выборка.Период;
	КонецЕсли;
	
	НоваяСтрока = ТЗРезультат.Добавить();
	ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
	НоваяСтрока.ТекущийПериод = Выборка.Период;
	НоваяСтрока.ПредПериод = ПредыдущийПериод;
	
	ПредыдущийПериод = Выборка.Период;
КонецЦикла;

Важные особенности и ограничения

← На главную