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

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

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

Главная сложность заключается в том, что в языке запросов 1С отсутствуют оконные функции (такие как LAG, LEAD или ROW_NUMBER() OVER (...)), доступные в классическом SQL. Если просто сгруппировать данные по валюте и курсу с получением МИНИМУМ(Период) и МАКСИМУМ(Период), результат окажется ошибочным: если курс валюты через некоторое время вернулся к прежнему значению, отдельные временные интервалы схлопнутся в один общий.

Принцип решения задачи на языке запросов 1С

Рассмотрим алгоритм решения этой задачи, известной в теории баз данных как "Gaps and Islands" (пробелы и острова). Нам необходимо разбить записи на группы так, чтобы каждая смена значения курса формировала новый номер интервала.

Разберем процесс по шагам:

  1. Подсчет количества смен состояний: Связываем исходную таблицу с самой собой по совпадению валюты и условию, что период предыдущей записи меньше или равен текущему периоду, а курс отличается от текущего. Количество таких "прошлых" отличающихся курсов задает уникальный порядковый номер интервала для текущей записи.
  2. Группировка по номеру интервала: Помещаем полученные данные с номерами интервалов во временную таблицу.
  3. Расчет границ интервала: В финальном запросе группируем записи по валюте, курсу и номеру интервала, получая МИНИМУМ(Период) в качестве даты начала и МАКСИМУМ(Период) в качестве даты окончания действия курса.

Пример запроса 1С

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


ВЫБРАТЬ
	ДАТАВРЕМЯ(2000, 1, 1) КАК Период,
	"USD" КАК Валюта,
	1.9 КАК Курс
ПОМЕСТИТЬ Дано

ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 2), "USD", 1.9
ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 3), "USD", 1.95
ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 4), "USD", 1.8
ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 5), "USD", 1.95
ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 1), "EUR", 2
ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 2), "EUR", 2
ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 3), "EUR", 2
ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 4), "EUR", 2
ОБЪЕДИНИТЬ ВЫБРАТЬ ДАТАВРЕМЯ(2000, 1, 5), "EUR", 2
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Дано.Период КАК Период,
	Дано.Валюта КАК Валюта,
	Дано.Курс КАК Курс,
	КОЛИЧЕСТВО(Было.Курс) КАК Номер
ПОМЕСТИТЬ Интервалы
ИЗ
	Дано КАК Дано
		ЛЕВОЕ СОЕДИНЕНИЕ Дано КАК Было
		ПО (Было.Валюта = Дано.Валюта)
			И (Было.Период <= Дано.Период)
			И (Было.Курс <> Дано.Курс)

СГРУППИРОВАТЬ ПО
	Дано.Период,
	Дано.Валюта,
	Дано.Курс
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	МИНИМУМ(Интервалы.Период) КАК ДатаС,
	МАКСИМУМ(Интервалы.Период) КАК ДатаПо,
	Интервалы.Валюта КАК Валюта,
	Интервалы.Курс КАК Курс
ИЗ
	Интервалы КАК Интервалы

СГРУППИРОВАТЬ ПО
	Интервалы.Валюта,
	Интервалы.Курс,
	Интервалы.Номер

УПОРЯДОЧИТЬ ПО
	Валюта,
	ДатаС

Оценка производительности и альтернативные решения

Описанный метод решения запросом имеет важную особенность: из-за неявного соединения таблицы «самой с собой» по условию Период <= Период сложность запроса составляет O(N²). На больших объемах данных в регистре (десятки тысяч записей) такой запрос вызовет высокую нагрузку на СУБД и может выполняться длительное время.

Рассмотрим способы оптимизации и альтернативные подходы:

  1. Предварительная фильтрация во временную таблицу: Если необходимо выполнить именно запрос (например, в отчете СКД), обязательно сначала отберите записи из регистра за нужный период по конкретным валютам во временную таблицу и проиндексируйте ее по полям Валюта и Период:
    
    ИНДЕКСИРОВАТЬ ПО
    	Валюта,
    	Период
    
    Это существенно снизит нагрузку на сервер баз данных.
  2. Программный обход на BSL (код 1С): В случаях, когда выборка обрабатывается программно, наиболее эффективным решением будет простой запрос с отбором и сортировкой по валюте и периоду (УПОРЯДОЧИТЬ ПО Валюта, Период). В цикле Пока Выборка.Следующий() КонецЦикла значения текущего курса сравниваются с предыдущим. При смене курса фиксируется ДатаПо предыдущего интервала и открывается новый с ДатаС. Этот подход работает за линейное время O(N) и не нагружает СУБД.
  3. Архитектурный подход (периодичность регистра): Если структура базы данных находится в разработке и подобный отчет требуется часто, имеет смысл хранить данные с явным указанием интервала окончания действия записи (поля ДатаНачала и ДатаОкончания).
← На главную