При работе с периодическими регистрами сведений в 1С программисты часто сталкиваются с особенностью их хранения: в базе фиксируется только дата начала действия значения (поле Период). Дата окончания действия явно не задана — она подразумевается как момент начала следующей записи. Однако для отчетов или расчетов часто требуется получить полноценные интервалы с колонками «Дата начала» и «Дата окончания».
В этой статье мы подробно разберем, как преобразовать плоский список записей регистра в таблицу периодов, рассмотрим способы оптимизации таких запросов и научимся объединять идущие подряд одинаковые значения.
Проанализируем самый распространенный подход, который работает на любых версиях платформы 1С v8. Основная идея заключается в том, чтобы для каждой записи найти в этом же регистре ближайшую к ней следующую запись по времени. Разница между текущим периодом и периодом следующей записи (минус одна секунда) и будет составлять интервал действия.
Рассмотрим пример запроса, где мы соединяем таблицу саму с собой:
ВЫБРАТЬ
Т1.Период КАК Начало,
МИНИМУМ(ДОБАВИТЬКДАТЕ(Т2.Период, СЕКУНДА, -1)) КАК Окончание,
Т1.Ставка КАК Ставка
ИЗ
РегистрСведений.СтавкиНалогов КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СтавкиНалогов КАК Т2
ПО Т1.Период < Т2.Период
И Т1.Объект = Т2.Объект
СГРУППИРОВАТЬ ПО
Т1.Период,
Т1.Ставка
Разберем по шагам, что здесь происходит:
Т1 (наши записи).Т2 по условию, что период в Т2 строго больше периода в Т1.МИНИМУМ, мы находим «самый первый из будущих» периодов. Это и есть начало следующего интервала.ДОБАВИТЬКДАТЕ мы вычитаем одну секунду, чтобы получить корректную границу текущего интервала (например, если следующая запись 01.03.2023 00:00:00, то текущая закончится 28.02.2023 23:59:59).Иногда удобнее использовать вложенный запрос в секции соединения или выбора. Этот метод часто считается более наглядным, хотя с точки зрения производительности на больших таблицах он может уступать группировке.
ВЫБРАТЬ
Т1.Период КАК Начало,
ЕСТЬNULL(ДОБАВИТЬКДАТЕ(
(ВЫБРАТЬ МИНИМУМ(Т2.Период)
ИЗ РегистрСведений.СтавкиНалогов КАК Т2
ГДЕ Т2.Период > Т1.Период), СЕКУНДА, -1),
ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)) КАК Окончание,
Т1.Ставка
ИЗ
РегистрСведений.СтавкиНалогов КАК Т1
Обратите внимание на использование ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59). Это критически важный момент: для последней записи в регистре «будущей» записи не существует, и вложенный запрос вернет NULL. Чтобы интервал не остался пустым, мы явно указываем «бесконечность».
Если вы работаете на современной версии платформы 1С (8.3.20 и выше), крайне рекомендуется использовать оконные функции. Это позволяет избежать тяжелого соединения таблицы «саму на себя», которое при миллионах записей может привести к значительным задержкам.
Выясним, как работает функция LEAD. Она позволяет «заглянуть» в следующую строку результата запроса без повторного сканирования таблицы.
ВЫБРАТЬ
Период КАК Начало,
ДОБАВИТЬКДАТЕ(
LEAD(Период) OVER (ORDER BY Период),
СЕКУНДА, -1) КАК Окончание,
Ставка
ИЗ
РегистрСведений.СтавкиНалогов
Преимущества этого метода очевидны: код становится короче, а СУБД выполняет задачу за один проход по данным. Это идеальный вариант для высоконагруженных систем.
Часто в регистре могут появиться записи, которые фактически не меняют значение, но создают новую точку во времени. Например:
01.01.2023 — Ставка 2%
15.01.2023 — Ставка 2%
01.02.2023 — Ставка 5%
Если мы будем использовать простые методы, описанные выше, мы получим два периода для ставки 2%. Но бизнес-логика часто требует «схлопнуть» их в один (с 01.01.2023 по 31.01.2023). Это классическая задача «островов».
Для решения проанализируем следующий алгоритм:
ROW_NUMBER()).При реализации выборки периодов стоит учитывать несколько технических моментов:
День, то вычитание секунды переведет дату на 23:59:59 предыдущего дня. Это корректно для отображения границ, но если вы планируете использовать эти данные для дальнейших соединений типа Между, убедитесь, что логика сравнения учитывает время.Т1.Период < Т2.Период крайне требователен к индексам. Если вы работаете с большой таблицей, сначала отфильтруйте нужные данные (например, по конкретному контрагенту или организации) во временную таблицу, проиндексируйте поле Период, и только потом выполняйте расчет интервалов.ДатаОкончания и заполнять его при записи. Это потребует дополнительного кода в модуле набора записей, но сделает чтение данных мгновенным.СрезПоследних.Подведем итог: Для простых задач и небольших объемов данных используйте классическое соединение с поиском МИНИМУМ(Период). Для современных конфигураций на платформе 8.3.20+ отдавайте предпочтение оконным функциям LEAD. А если требуется объединение одинаковых значений, используйте технику группировки по разности номеров строк.