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

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

При работе с периодическими регистрами сведений в 1С программисты часто сталкиваются с особенностью их хранения: в базе фиксируется только дата начала действия значения (поле Период). Дата окончания действия явно не задана — она подразумевается как момент начала следующей записи. Однако для отчетов или расчетов часто требуется получить полноценные интервалы с колонками «Дата начала» и «Дата окончания».

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

Метод 1: Классическое левое соединение (поиск следующей даты)

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

Рассмотрим пример запроса, где мы соединяем таблицу саму с собой:


ВЫБРАТЬ
    Т1.Период КАК Начало,
    МИНИМУМ(ДОБАВИТЬКДАТЕ(Т2.Период, СЕКУНДА, -1)) КАК Окончание,
    Т1.Ставка КАК Ставка
ИЗ
    РегистрСведений.СтавкиНалогов КАК Т1
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтавкиНалогов КАК Т2
        ПО Т1.Период < Т2.Период
        И Т1.Объект = Т2.Объект
СГРУППИРОВАТЬ ПО
    Т1.Период,
    Т1.Ставка

Разберем по шагам, что здесь происходит:

  1. Мы берем основную таблицу Т1 (наши записи).
  2. Присоединяем к ней ту же таблицу Т2 по условию, что период в Т2 строго больше периода в Т1.
  3. Используя функцию МИНИМУМ, мы находим «самый первый из будущих» периодов. Это и есть начало следующего интервала.
  4. С помощью ДОБАВИТЬКДАТЕ мы вычитаем одну секунду, чтобы получить корректную границу текущего интервала (например, если следующая запись 01.03.2023 00:00:00, то текущая закончится 28.02.2023 23:59:59).

Метод 2: Использование вложенного запроса (коррелированный запрос)

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


ВЫБРАТЬ
    Т1.Период КАК Начало,
    ЕСТЬNULL(ДОБАВИТЬКДАТЕ(
        (ВЫБРАТЬ МИНИМУМ(Т2.Период)
         ИЗ РегистрСведений.СтавкиНалогов КАК Т2
         ГДЕ Т2.Период > Т1.Период), СЕКУНДА, -1),
        ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)) КАК Окончание,
    Т1.Ставка
ИЗ
    РегистрСведений.СтавкиНалогов КАК Т1

Обратите внимание на использование ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59). Это критически важный момент: для последней записи в регистре «будущей» записи не существует, и вложенный запрос вернет NULL. Чтобы интервал не остался пустым, мы явно указываем «бесконечность».

Метод 3: Оптимизация через оконные функции (Платформа 8.3.20+)

Если вы работаете на современной версии платформы 1С (8.3.20 и выше), крайне рекомендуется использовать оконные функции. Это позволяет избежать тяжелого соединения таблицы «саму на себя», которое при миллионах записей может привести к значительным задержкам.

Выясним, как работает функция LEAD. Она позволяет «заглянуть» в следующую строку результата запроса без повторного сканирования таблицы.


ВЫБРАТЬ
    Период КАК Начало,
    ДОБАВИТЬКДАТЕ(
        LEAD(Период) OVER (ORDER BY Период),
        СЕКУНДА, -1) КАК Окончание,
    Ставка
ИЗ
    РегистрСведений.СтавкиНалогов

Преимущества этого метода очевидны: код становится короче, а СУБД выполняет задачу за один проход по данным. Это идеальный вариант для высоконагруженных систем.

Проблема «островов» и «промежутков» (Islands and Gaps)

Часто в регистре могут появиться записи, которые фактически не меняют значение, но создают новую точку во времени. Например:
01.01.2023 — Ставка 2%
15.01.2023 — Ставка 2%
01.02.2023 — Ставка 5%

Если мы будем использовать простые методы, описанные выше, мы получим два периода для ставки 2%. Но бизнес-логика часто требует «схлопнуть» их в один (с 01.01.2023 по 31.01.2023). Это классическая задача «островов».

Для решения проанализируем следующий алгоритм:

  1. Вычисляем порядковый номер всех записей (ROW_NUMBER()).
  2. Вычисляем порядковый номер записей внутри группы с одинаковой ставкой.
  3. Разность между общим номером и номером в группе будет одинаковой для всех записей, идущих подряд с одним и тем же значением.
  4. Группируем по этой разности и получаем минимальный/максимальный период.

Важные нюансы и рекомендации

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

  1. Точность времени: Если ваш регистр имеет периодичность День, то вычитание секунды переведет дату на 23:59:59 предыдущего дня. Это корректно для отображения границ, но если вы планируете использовать эти данные для дальнейших соединений типа Между, убедитесь, что логика сравнения учитывает время.
  2. Индексация: Любой запрос с условием Т1.Период < Т2.Период крайне требователен к индексам. Если вы работаете с большой таблицей, сначала отфильтруйте нужные данные (например, по конкретному контрагенту или организации) во временную таблицу, проиндексируйте поле Период, и только потом выполняйте расчет интервалов.
  3. Хранение границ: Как было отмечено в обсуждении, если такие запросы выполняются слишком часто и тормозят систему, рассмотрите возможность изменения архитектуры. Иногда выгоднее добавить в регистр сведений ресурс ДатаОкончания и заполнять его при записи. Это потребует дополнительного кода в модуле набора записей, но сделает чтение данных мгновенным.
  4. СрезПоследних на каждую дату: Если вам нужно получить состояние на каждый день календаря (включая дни, когда записей в регистре не было), лучше использовать левое соединение производственного календаря с виртуальной таблицей СрезПоследних.

Подведем итог: Для простых задач и небольших объемов данных используйте классическое соединение с поиском МИНИМУМ(Период). Для современных конфигураций на платформе 8.3.20+ отдавайте предпочтение оконным функциям LEAD. А если требуется объединение одинаковых значений, используйте технику группировки по разности номеров строк.

← На главную