Как правильно реализовать наложение и пересечение периодов в запросах 1С

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

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

Математическая логика пересечения интервалов

Прежде чем переходить к написанию кода, проанализируем условие, при котором два периода вообще имеют общие точки. Пусть первый период задан границами [ДатаНачала1, ДатаОкончания1], а второй — [ДатаНачала2, ДатаОкончания2].

Периоды пересекаются тогда и только тогда, когда выполняется условие: Начало одного периода меньше или равно окончанию другого. В терминах 1С это выглядит так:

ДатаНачала1 <= ДатаОкончания2 И ДатаОкончания1 >= ДатаНачала2

Если это условие выполняется, то у периодов есть общая часть. Границы этого пересечения определяются по следующему правилу:

  1. Новое Начало — это максимум из двух начал.
  2. Новое Окончание — это минимум из двух окончаний.

Шаг 1. Соединение таблиц по условию пересечения

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

Посмотрим на пример базового соединения:


ВЫБРАТЬ
    ТаблицаПериодов.ДатаНачалаНачисления КАК ДатаНачалаНачисления,
    ТаблицаПериодов.ДатаОкончанияНачисления КАК ДатаОкончанияНачисления,
    ТаблицаПериодовСтажа.ДатаНачалаСтажа КАК ДатаНачалаСтажа,
    ТаблицаПериодовСтажа.ДатаОкончанияСтажа КАК ДатаОкончанияСтажа,
    ТаблицаПериодовСтажа.Процент КАК Процент
ИЗ
    ТаблицаПериодов КАК ТаблицаПериодов
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТаблицаПериодовСтажа КАК ТаблицаПериодовСтажа
        ПО ТаблицаПериодов.ДатаНачалаНачисления <= ТаблицаПериодовСтажа.ДатаОкончанияСтажа
            И ТаблицаПериодов.ДатаОкончанияНачисления >= ТаблицаПериодовСтажа.ДатаНачалаСтажа

Важный нюанс: Такое соединение просто выберет строки, которые пересекаются. Если мы оставим выбор полей в таком виде, мы получим исходные даты, а не границы «разрезанного» периода. Например, для периода начисления с 15.05 по 29.05 и периода стажа с 01.05 по 18.05 мы получим строку с исходными датами, что не решит задачу.

Шаг 2. Вычисление границ пересечения (имитация МИН и МАКС)

Поскольку в языке запросов 1С отсутствуют скалярные функции МИН и МАКС для сравнения двух значений в одной строке (агрегатные функции здесь не подходят), мы должны использовать конструкцию ВЫБОР.

Разберем, как вычислить правильные даты пересечения:


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

Проанализируем работу этого кода на примере. Если период начисления 15.05–29.05 «встречается» с периодом стажа 01.05–18.05:

Шаг 3. Учет времени и точность границ

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

Рекомендуем всегда приводить даты к началу или концу периода. Выясним причину возможных ошибок: если период стажа заканчивается 18.05 в 00:00:00 (фактически это начало дня), а начисление начинается 18.05 в 09:00:00, то по условию <= и >= они пересекутся, но фактически это могут быть разные логические периоды.

Для надежности используйте функции НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА:


ПО НАЧАЛОПЕРИОДА(ТаблицаПериодов.ДатаНачалаНачисления, ДЕНЬ) <= КОНЕЦПЕРИОДА(ТаблицаПериодовСтажа.ДатаОкончанияСтажа, ДЕНЬ)
    И КОНЕЦПЕРИОДА(ТаблицаПериодов.ДатаОкончанияНачисления, ДЕНЬ) >= НАЧАЛОПЕРИОДА(ТаблицаПериодовСтажа.ДатаНачалаСтажа, ДЕНЬ)

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

Когда мы соединяем таблицы по условиям «больше-меньше», СУБД выполняет более тяжелую работу, чем при соединении по точному равенству. Чтобы запрос работал быстро на больших объемах данных, следует соблюдать правила:

  1. Индексация: Если вы используете временные таблицы, обязательно создавайте индексы по полям дат. Это позволит оптимизатору запросов SQL эффективнее обрабатывать диапазоны.
  2. Предварительная фильтрация: Если вам нужен расчет только по одному сотруднику или за один месяц, наложите эти фильтры в предложении ГДЕ или при выборе из физической таблицы в виртуальную, чтобы не соединять лишние данные.
  3. Проверка пустых периодов: В коде на языке запросов 1С соединение ВНУТРЕННЕЕ само отсечет ситуации, когда пересечения нет. Однако, если вы используете ЛЕВОЕ СОЕДИНЕНИЕ, обязательно проверяйте результат на NULL и корректность полученных дат (Начало не должно быть больше Окончания).

Альтернативный метод: Разворачивание в таблицу дат

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

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

Подводя итог, можно сказать, что решение через INNER JOIN и ВЫБОР является наиболее универсальным и производительным способом работы с периодами в 1С. Оно позволяет получить чистый и понятный результат без использования циклов в коде Объектной модели.

← На главную