Как правильно объединить и разрезать пересекающиеся периоды действия цен в 1С

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

Работа с временными интервалами — одна из самых трудоемких задач в разработке на платформе 1С. Часто возникает ситуация, когда в систему вводятся документы установки цен, периоды действия которых накладываются друг на друга. Например, одна цена установлена на весь год, а в середине года вводится промо-цена на две недели. Нам необходимо построить итоговую цепочку периодов, где каждый отрезок времени имеет только одно актуальное значение цены, а периоды не пересекаются.

В этой статье мы подробно разберем, как реализовать механизм «разрезания» и слияния интервалов с помощью языка запросов и программных методов.

Анализ проблемы пересекающихся интервалов

Рассмотрим ситуацию: у нас есть исходная таблица с полями Начало, Окончание и Цена. Главная сложность заключается в том, что новый документ может «вклиниться» внутрь существующего периода, полностью его перекрыть или сдвинуть его границы. Чтобы решить задачу чисто запросом, нам нужно отойти от понятия «документ» и перейти к понятию «граничная точка времени».

Проанализируем основные этапы решения задачи:

  1. Сбор всех уникальных дат: Все даты начала и окончания из всех документов должны стать границами наших будущих «элементарных» отрезков.
  2. Формирование новой сетки периодов: Из полученных уникальных дат нужно составить непрерывную последовательность интервалов «от и до».
  3. Сопоставление цены: Для каждого элементарного интервала нужно определить, какая цена в нем действовала, учитывая приоритеты (например, более позднюю дату документа).

Решение методом формирования временной сетки в запросе

Рассмотрим наиболее эффективный подход с использованием временных таблиц. Этот метод позволяет решить задачу на уровне СУБД, что критически важно для производительности при больших объемах данных.

Шаг 1. Собираем все точки перелома периодов

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


ВЫБРАТЬ Начало КАК ДатаГраницы ПОМЕСТИТЬ ВсеДаты ИЗ ИсхТаблица
ОБЪЕДИНИТЬ
ВЫБРАТЬ Окончание ИЗ ИсхТаблица;

Шаг 2. Формируем элементарные интервалы

Теперь проанализируем список дат и составим из них пары. Для каждой даты нам нужно найти ближайшую к ней дату «в будущем». Рассмотрим, как это сделать через соединение таблицы самой с собой.


ВЫБРАТЬ
    Т1.ДатаГраницы КАК НачалоПериода,
    МИНИМУМ(Т2.ДатаГраницы) КАК КонецПериода
ПОМЕСТИТЬ СеткаИнтервалов
ИЗ
    ВсеДаты КАК Т1
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВсеДаты КАК Т2 ПО Т1.ДатаГраницы < Т2.ДатаГраницы
СГРУППИРОВАТЬ ПО
    Т1.ДатаГраницы;

В результате мы получили таблицу СеткаИнтервалов, которая содержит «нарезанные» периоды, следующие друг за другом без пропусков и наложений.

Определение актуальной цены для каждого интервала

После того как сетка готова, нам нужно «наложить» на нее наши исходные данные. Рассмотрим ситуацию, когда на один элементарный интервал претендуют несколько цен. В этом случае вводится понятие приоритета. Чаще всего приоритетной считается та запись, которая была внесена в систему позже (имеет больший МоментВремени или ДатаРегистрации).

Выясним, как соединить интервалы с данными:


ВЫБРАТЬ
    Сетка.НачалоПериода,
    Сетка.КонецПериода,
    МАКСИМУМ(Исходники.Цена) КАК Цена // Здесь может быть сложная логика выбора по приоритету
ИЗ
    СеткаИнтервалов КАК Сетка
    ЛЕВОЕ СОЕДИНЕНИЕ ИсхТаблица КАК Исходники 
        ПО Сетка.НачалоПериода >= Исходники.Начало 
        И Сетка.КонецПериода <= Исходники.Окончание
СГРУППИРОВАТЬ ПО
    Сетка.НачалоПериода,
    Сетка.КонецПериода;

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

Алгоритмический подход через таблицу значений

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

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

  1. Выгрузим все записи в таблицу значений и отсортируем их по полю Начало.
  2. Создадим результирующую таблицу.
  3. Будем итерироваться по исходным строкам, сравнивая текущий период со следующим.
  4. Если текущий период перекрывает следующий, мы «разрезаем» текущую строку, добавляем ее в результат, а остаток переносим на следующую итерацию.

Такой метод позволяет легко реализовать логику «вытеснения», знакомую многим по работе с механизмом зарплаты в 1С. Кстати, в платформе 1С объект РегистрРасчета обладает встроенной виртуальной таблицей ФактическийПериодДействия, которая делает именно это — автоматически разрезает периоды с учетом приоритетов видов расчета. Если ваша задача масштабна, можно проанализировать возможность использования этого объекта, даже если она не связана напрямую с расчетом зарплаты.

Работа с «пустыми» датами и бесконечностью

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

Рекомендуем приводить открытые периоды к «дате бесконечности» (например, 31.12.3999). Рассмотрим пример использования функции ВЫБОР в запросе для корректной обработки таких случаев:


ВЫБРАТЬ
    Начало,
    ВЫБОР 
        КОГДА Окончание = ДАТАВРЕМЯ(1, 1, 1) ТОГДА ДАТАВРЕМЯ(3999, 12, 31) 
        ИНАЧЕ Окончание 
    КОНЕЦ КАК КонецПериода
ИЗ
    РегистрСведений.Цены;

Это гарантирует, что при соединении с таблицей ВсеДаты открытый период будет корректно участвовать в формировании сетки интервалов и не «выпадет» из логики МИНИМУМ(Т2.ДатаГраницы).

Оптимизация и «схлопывание» одинаковых периодов

После «разрезания» интервалов часто возникают ситуации, когда два соседних периода имеют одну и ту же цену (например, если два исходных документа имели одинаковую цену и стояли встык). В финальном результате их желательно объединить обратно. Эта задача известна как «Gaps and Islands».

Посмотрим, как это реализовать:

  1. Для каждой строки определим, совпадает ли её цена с ценой предыдущей строки (через соединение по Текущее.Начало = Предыдущее.Конец).
  2. Если цены совпадают, пометим такую строку как «продолжение».
  3. Сгруппируем идущие подряд «продолжения», взяв МИНИМУМ даты начала первой строки группы и МАКСИМУМ даты окончания последней.

Этот финальный шаг сделает ваш отчет или регистр чистым и удобным для анализа пользователями, убирая лишнюю «дробность» данных.

← На главную