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

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

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

Постановка задачи и логика решения

Проанализируем ситуацию: у нас есть периодический регистр (например, "Курсы валют" или "Цены номенклатуры"). Каждая новая запись в нем фактически означает окончание действия предыдущей. Таким образом, датой окончания текущей записи будет являться дата начала следующей записи за вычетом минимальной единицы времени (обычно 1 секунда), либо сама дата начала следующей записи, если мы допускаем нестрогие границы интервалов.

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

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

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

Разберем этот алгоритм по шагам:

  1. Выбираем основную таблицу регистра (назовем её Т1).
  2. Присоединяем к ней ту же самую таблицу (назовем её Т2) левым соединением.
  3. В условии связи указываем равенство всех значимых измерений (например, Валюта или Номенклатура) и условие Т1.Период < Т2.Период.
  4. Группируем результат по полям первой таблицы.
  5. Для получения даты окончания используем функцию МИНИМУМ(Т2.Период). Если следующей записи нет (результат NULL), подставляем максимально возможную дату в системе.

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


ВЫБРАТЬ
    КурсыВалют.Период КАК НачалоДействия,
    КурсыВалют.Валюта КАК Валюта,
    КурсыВалют.Курс КАК Курс,
    ЕСТЬNULL(МИНИМУМ(СледующиеКурсы.Период), ДАТАВРЕМЯ(3999, 12, 31)) КАК КонецДействия
ИЗ
    РегистрСведений.КурсыВалют КАК КурсыВалют
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК СледующиеКурсы
        ПО КурсыВалют.Валюта = СледующиеКурсы.Валюта
            И КурсыВалют.Период < СледующиеКурсы.Период
СГРУППИРОВАТЬ ПО
    КурсыВалют.Период,
    КурсыВалют.Валюта,
    КурсыВалют.Курс

Важный нюанс: Чтобы интервалы не "накладывались" друг на друга (когда одна секунда принадлежит одновременно концу одного периода и началу другого), часто из вычисленной даты окончания вычитают одну секунду. Для этого в языке запросов используется функция ДОБАВИТЬКДАТЕ.

Способ 2. Оконные функции (Платформа 8.3.20 и выше)

Если ваша конфигурация работает на современной версии платформы, использование левого соединения для поиска следующей даты — это "вчерашний день". Оконные функции позволяют вычислить значение следующей строки гораздо быстрее, так как СУБД делает это за один проход по данным, не создавая тяжелых декартовых произведений при соединении.

Рассмотрим использование функции СЛЕДУЮЩЕЕ (аналог SQL-функции LEAD). Выясним, как упрощается запрос:


ВЫБРАТЬ
    КурсыВалют.Период КАК НачалоДействия,
    КурсыВалют.Валюта КАК Валюта,
    КурсыВалют.Курс КАК Курс,
    СЛЕДУЮЩЕЕ(КурсыВалют.Период) ОПОРЯДОЧИТЬ ПО КурсыВалют.Период ПАРТИЦИЯ ПО КурсыВалют.Валюта КАК КонецДействия
ИЗ
    РегистрСведений.КурсыВалют КАК КурсыВалют

В данном примере ПАРТИЦИЯ ПО группирует данные по валюте, а ОПОРЯДОЧИТЬ ПО гарантирует, что система возьмет период именно следующей хронологической записи для конкретной валюты. Это решение в разы производительнее на больших объемах данных.

Способ 3. Вычисление границ в Системе компоновки данных (СКД)

Иногда программисту не нужно программно получать таблицу в коде, а достаточно вывести отчет с интервалами. В этом случае мы можем переложить задачу расчета даты окончания на механизмы СКД. Проанализируем возможности функции ВычислитьВыражение.

В настройках ресурсов или вычисляемых полей СКД можно прописать выражение:

ВычислитьВыражение("Период", , , "Следующая", "Период")

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

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

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

  1. Использование временных таблиц: Никогда не делайте самосоединение на физической таблице регистра напрямую. Сначала выгрузите нужный срез данных (например, по конкретному списку товаров или за определенный год) во временную таблицу.
  2. Индексация: Обязательно индексируйте временную таблицу по полям, участвующим в связи (измерения + период). Без индексов СУБД будет вынуждена сканировать всю таблицу для каждой строки.
  3. Ограничение выборки: Старайтесь максимально сузить диапазон данных еще на этапе помещения во временную таблицу.

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


// Помещаем данные во временную таблицу
ВЫБРАТЬ
    Регистр.Период,
    Регистр.Номенклатура,
    Регистр.Цена
ПОМЕСТИТЬ ВТ_Цены
ИЗ
    РегистрСведений.ЦеныНоменклатуры КАК Регистр
ГДЕ
    Регистр.Номенклатура В (&СписокНоменклатуры)
ИНДЕКСИРОВАТЬ ПО
    Номенклатура,
    Период;

// Соединяем временную таблицу
ВЫБРАТЬ
    Т1.Период КАК ДатаНачала,
    ЕСТЬNULL(ДОБАВИТЬКДАТЕ(МИНИМУМ(Т2.Период), СЕКУНДА, -1), ДАТАВРЕМЯ(3999, 12, 31)) КАК ДатаОкончания,
    Т1.Номенклатура,
    Т1.Цена
ИЗ
    ВТ_Цены КАК Т1
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Цены КАК Т2
        ПО Т1.Номенклатура = Т2.Номенклатура
            И Т1.Период < Т2.Период
СГРУППИРОВАТЬ ПО
    Т1.Период,
    Т1.Номенклатура,
    Т1.Цена

Архитектурный совет: Интервальные регистры

Если в вашей бизнес-логике задача получения интервалов (от и до) встречается постоянно (например, при расчете аренды, начислении зарплаты или анализе занятости ресурсов), возможно, стоит пересмотреть структуру данных. Опытные архитекторы в таких случаях создают непериодический регистр сведений, в котором явно присутствуют два измерения типа Дата: ДатаНачала и ДатаОкончания.

Хотя такой подход требует написания кода для контроля "пересечений" периодов при записи, он делает запросы на чтение данных максимально простыми и быстрыми. Вам больше не нужно будет соединять таблицы или использовать сложные функции — достаточно будет простого условия ГДЕ &НужнаяДата МЕЖДУ ДатаНачала И ДатаОкончания.

Подведем итог: для разовых задач в старых системах используйте левое соединение с группировкой по минимуму следующего периода. В новых системах отдавайте предпочтение оконным функциям СЛЕДУЮЩЕЕ. А для высоконагруженных отчетов используйте функции СКД.

← На главную