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

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

В практике программирования на платформе 1С:Предприятие часто возникает задача очистки данных от пересекающихся или избыточных временных интервалов. Типичный пример: сотрудник имеет два пересекающихся периода работы (например, с 1 по 2 сентября и с 1 по 4 сентября). С точки зрения логики учета, первый интервал полностью поглощается вторым, и для отчетов или расчетов нам необходимо оставить только один — наиболее полный или объединить их в один непрерывный период.

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

Метод дискретизации интервалов (разбиение по дням)

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

Шаг 1: Подготовка исходных данных

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


ВЫБРАТЬ
    Сотр.Сотрудник КАК Сотрудник,
    Сотр.ДатаНачала КАК ДатаНачала,
    Сотр.ДатаОкончания КАК ДатаОкончания
ПОМЕСТИТЬ ВТСотрудникиДаты
ИЗ
    &Сотр КАК Сотр;

Шаг 2: Создание вспомогательного календаря

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


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ВТСотрудникиДаты.Сотрудник КАК Сотрудник,
    ВТКалендарь.Дата КАК Дата
ПОМЕСТИТЬ ВТДаты
ИЗ
    ВТСотрудникиДаты КАК ВТСотрудникиДаты
    ЛЕВОЕ СОЕДИНЕНИЕ ВТКалендарь КАК ВТКалендарь
    ПО ВТСотрудникиДаты.ДатаНачала <= ВТКалендарь.Дата
        И ВТКалендарь.Дата <= ВТСотрудникиДаты.ДатаОкончания;

Использование ключевого слова РАЗЛИЧНЫЕ здесь критически важно. Если у Сотрудник1 было два пересекающихся периода (например, 01.09-02.09 и 01.09-04.09), то после соединения с календарем для дат 01.09 и 02.09 у нас возникнет дублирование строк. Оператор РАЗЛИЧНЫЕ "схлопывает" эти дубли, оставляя уникальный набор дней, когда сотрудник был занят, независимо от количества пересекающихся документов.

Шаг 3: Нумерация строк для определения "островов"

Теперь нам нужно превратить набор дат обратно в периоды. Основная сложность здесь заключается в том, что у сотрудника может быть несколько разных (не пересекающихся) периодов. Например, с 1 по 2 сентября и с 4 по 5 сентября. Мы не можем просто взять МИНИМУМ и МАКСИМУМ дат, иначе получим один большой период с 1 по 5 сентября, что будет ошибкой (3 сентября выпадет).

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

  1. 01.09 (номер 1) -> 01.09 - 1 день = 31.08
  2. 02.09 (номер 2) -> 02.09 - 2 дня = 31.08
  3. 04.09 (номер 3) -> 04.09 - 3 дня = 01.09

Как мы видим, для непрерывного блока (1 и 2 число) получилось одинаковое контрольное значение (31.08). Для даты после разрыва (4 число) значение изменилось. Это позволяет нам сгруппировать данные.

В запросе 1С нумерация строк реализуется через соединение таблицы самой с собой:


ВЫБРАТЬ
    Даты.Дата КАК Дата,
    Даты.Сотрудник КАК Сотрудник,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ДатыДо.Дата) КАК Номер
ПОМЕСТИТЬ НомераДат
ИЗ
    ВТДаты КАК Даты
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТДаты КАК ДатыДо
    ПО ДатыДо.Дата <= Даты.Дата
        И ДатыДо.Сотрудник = Даты.Сотрудник
СГРУППИРОВАТЬ ПО
    Даты.Дата,
    Даты.Сотрудник;

Шаг 4: Финальная сборка периодов

Проанализируем итоговый запрос. Мы группируем данные по сотруднику и по вычисленной разности даты и её номера. Внутри каждой такой группы минимальная дата станет началом интервала, а максимальная — окончанием.


ВЫБРАТЬ
    МИНИМУМ(НомераДат.Дата) КАК ДатаНач,
    МАКСИМУМ(НомераДат.Дата) КАК ДатаКон,
    НомераДат.Сотрудник КАК Сотрудник    
ИЗ
    НомераДат КАК НомераДат
СГРУППИРОВАТЬ ПО
    НомераДат.Сотрудник,
    ДОБАВИТЬКДАТЕ(НомераДат.Дата, ДЕНЬ, -НомераДат.Номер);

Альтернативный метод: Исключение вложенных интервалов

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

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


ВЫБРАТЬ
    Т1.Сотрудник,
    Т1.ДатаНачала,
    Т1.ДатаОкончания
ИЗ
    ВТСотрудникиДаты КАК Т1
ГДЕ
    НЕ ВЫБРАТЬ 1 ИЗ ВТСотрудникиДаты КАК Т2
       ГДЕ Т2.Сотрудник = Т1.Сотрудник
       И Т2.ДатаНачала <= Т1.ДатаНачала
       И Т2.ДатаОкончания >= Т1.ДатаОкончания
       И (Т2.ДатаНачала < Т1.ДатаНачала ИЛИ Т2.ДатаОкончания > Т1.ДатаОкончания);

Выясним причину использования условий в скобках: они нужны, чтобы строка не исключила саму себя. Этот метод эффективен для очистки "мусора", но он не склеит два периода вида 01.09-03.09 и 02.09-04.09 в один (01.09-04.09), так как ни один из них не является строго вложенным в другой.

Особенности работы с временем

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

  1. Если интервал заканчивается 02.09.2023 23:59:59, а следующий начинается 03.09.2023 00:00:00, метод с календарем по дням может посчитать их непрерывными.
  2. Если точность данных выше одного дня (например, интервалы внутри дня по часам), метод с календарным соединением потребует таблицу не дат, а секунд, что приведет к катастрофическому падению производительности. В таких случаях следует использовать событийный подход (расчет нарастающим итогом точек начала и конца периодов).

Использование оконных функций (Платформа 8.3.20+)

В современных версиях платформы 1С задачу "Gaps and Islands" (Разрывы и Острова) можно решить без тяжелого соединения ДатаДо <= Дата. Рассмотрим логику с применением оконной функции АВТОНОМЕРЗАПИСИ (аналог ROW_NUMBER()), которая позволяет пронумеровать строки внутри группы сотрудника, упорядоченных по дате начала. Это избавляет от необходимости генерировать календарь и позволяет работать с любыми интервалами, не привязываясь к дискретным дням.

Резюме: выбор метода зависит от ваших целей. Если нужно просто удалить дубли и вложенные периоды — используйте ЛЕВОЕ СОЕДИНЕНИЕ или ГДЕ НЕ В. Если требуется полноценная склейка любых пересекающихся интервалов в один — используйте метод с нумерацией строк или событийный алгоритм контрольных точек.

← На главную