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

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

При решении задач планирования, учета рабочего времени и анализа загрузки оборудования часто возникает необходимость сопоставить несколько графиков. Например, у нас есть суточный график работы подразделения с перерывами и фактические интервалы выполнения технологических операций. Если наложить их друг на друга, интервалы пересекаются частично или полностью, образуя отрезки с разными свойствами: «рабочее время без задач», «рабочее время с выполнением задачи 1», «перерыв во время выполнения задачи 2» и так далее.

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

Постановка задачи

Проанализируем типовую ситуацию. Пусть задан суточный график сменности:

  1. 00:00 – 08:00 — нерабочее время;
  2. 08:00 – 12:00 — рабочее время;
  3. 12:00 – 13:00 — нерабочее время (обед);
  4. 13:00 – 17:00 — рабочее время;
  5. 17:00 – 24:00 — нерабочее время.

Параллельно зафиксированы интервалы выполнения работ:

  1. 09:00 – 11:00 — 1-я работа;
  2. 12:00 – 16:00 — 2-я работа.

Наша цель — получить единую непрерывную шкалу, где каждое изменение статуса формирует отдельную строку без наложений и пропусков времени.

Способ 1. Метод оси времени и элементарных интервалов

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

  1. Собираем все точки времени (начала и окончания всех интервалов обеих таблиц, а также границы суток 00:00 и 24:00) в единый список без дублей с помощью конструкции ОБЪЕДИНИТЬ.
  2. Для каждой точки определяем ближайшую следующую за ней точку во времени. Пара соседних меток образует минимальный элементарный неделимый интервал.
  3. К полученным элементарным отрезкам левым соединением подключаем исходные таблицы интервалов по условию вхождения отрезка внутрь периода.

Посмотрим на реализацию этого подхода в тексте запроса:


ВЫБРАТЬ 8 КАК От, 12 КАК До, "раб. время" КАК ТипВремени
ПОМЕСТИТЬ ГрафикПоКалендарю
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 13, 17, "раб. время"
;

ВЫБРАТЬ 9 КАК От, 11 КАК До, "1-ая работа" КАК Работа
ПОМЕСТИТЬ Загрузка
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 12, 16, "2-я работа"
;

ВЫБРАТЬ
    0 КАК Момент
ПОМЕСТИТЬ ОсьВремени

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    График.От
ИЗ
    ГрафикПоКалендарю КАК График

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    График.До
ИЗ
    ГрафикПоКалендарю КАК График

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    Загрузка.От
ИЗ
    Загрузка КАК Загрузка

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    Загрузка.До
ИЗ
    Загрузка КАК Загрузка

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    24
;

ВЫБРАТЬ
    ОсьВремени.Момент КАК От,
    МИНИМУМ(Позже.Момент) КАК До
ПОМЕСТИТЬ ВсеИнтервалы
ИЗ
    ОсьВремени КАК ОсьВремени
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ОсьВремени КАК Позже
        ПО ОсьВремени.Момент < Позже.Момент
СГРУППИРОВАТЬ ПО
    ОсьВремени.Момент
;

ВЫБРАТЬ
    ВсеИнтервалы.От,
    ВсеИнтервалы.До,
    ЕСТЬNULL(ГрафикПоКалендарю.ТипВремени, "нераб. время") КАК ТипВремени,
    ЕСТЬNULL(Загрузка.Работа, "без работы") КАК Работа
ИЗ
    ВсеИнтервалы КАК ВсеИнтервалы
        ЛЕВОЕ СОЕДИНЕНИЕ ГрафикПоКалендарю КАК ГрафикПоКалендарю
        ПО (ГрафикПоКалендарю.От <= ВсеИнтервалы.От)
            И (ВсеИнтервалы.До <= ГрафикПоКалендарю.До)
        ЛЕВОЕ СОЕДИНЕНИЕ Загрузка КАК Загрузка
        ПО (Загрузка.От <= ВсеИнтервалы.От)
            И (ВсеИнтервалы.До <= Загрузка.До)
УПОРЯДОЧИТЬ ПО
    ВсеИнтервалы.От

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

Способ 2. Вычисление левых, правых интервалов и пересечений через объединение

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

  1. Центральная часть: само пересечение (максимум из начал и минимум из окончаний).
  2. Левый «хвост»: отрезок от начала более раннего интервала до начала пересечения.
  3. Правый «хвост»: отрезок от окончания пересечения до завершения более позднего интервала.

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


ВЫБРАТЬ
    ВЫБОР
        КОГДА ИнтервалыДня.От >= ЕСТЬNULL(ИнтервалыРабот.От, ДАТАВРЕМЯ(1, 1, 1))
            ТОГДА ИнтервалыДня.От
        ИНАЧЕ ЕСТЬNULL(ИнтервалыРабот.От, ДАТАВРЕМЯ(1, 1, 1))
    КОНЕЦ КАК От,
    ВЫБОР
        КОГДА ИнтервалыДня.До <= ЕСТЬNULL(ИнтервалыРабот.До, ДАТАВРЕМЯ(1, 1, 2))
            ТОГДА ИнтервалыДня.До
        ИНАЧЕ ЕСТЬNULL(ИнтервалыРабот.До, ДАТАВРЕМЯ(1, 1, 2))
    КОНЕЦ КАК До,
    ИнтервалыДня.ТипВремени КАК ТипВремени,
    ИнтервалыРабот.Работа КАК Работа
ИЗ
    ИнтервалыДня КАК ИнтервалыДня
        ЛЕВОЕ СОЕДИНЕНИЕ ИнтервалыРабот КАК ИнтервалыРабот
        ПО (ИнтервалыРабот.От <= ИнтервалыДня.До)
            И (ИнтервалыРабот.До >= ИнтервалыДня.От)
ГДЕ
    // Отсекаем интервалы нулевой длины (точки касания)
    ВЫБОР
        КОГДА ИнтервалыДня.От >= ЕСТЬNULL(ИнтервалыРабот.От, ДАТАВРЕМЯ(1, 1, 1))
            ТОГДА ИнтервалыДня.От
        ИНАЧЕ ЕСТЬNULL(ИнтервалыРабот.От, ДАТАВРЕМЯ(1, 1, 1))
    КОНЕЦ <>
    ВЫБОР
        КОГДА ИнтервалыДня.До <= ЕСТЬNULL(ИнтервалыРабот.До, ДАТАВРЕМЯ(1, 1, 2))
            ТОГДА ИнтервалыДня.До
        ИНАЧЕ ЕСТЬNULL(ИнтервалыРабот.До, ДАТАВРЕМЯ(1, 1, 2))
    КОНЕЦ

ОБЪЕДИНИТЬ ВСЕ

// Левые интервалы
ВЫБРАТЬ
    ИнтервалыДня.От,
    ИнтервалыРабот.От,
    ИнтервалыДня.ТипВремени,
    ""
ИЗ
    ИнтервалыДня КАК ИнтервалыДня
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ИнтервалыРабот КАК ИнтервалыРабот
        ПО (ИнтервалыРабот.От < ИнтервалыДня.До)
            И (ИнтервалыРабот.До >= ИнтервалыДня.От)
            И (ИнтервалыРабот.От > ИнтервалыДня.От)

ОБЪЕДИНИТЬ ВСЕ

// Правые интервалы
ВЫБРАТЬ
    ИнтервалыРабот.До,
    ИнтервалыДня.До,
    ИнтервалыДня.ТипВремени,
    ""
ИЗ
    ИнтервалыДня КАК ИнтервалыДня
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ИнтервалыРабот КАК ИнтервалыРабот
        ПО (ИнтервалыРабот.От < ИнтервалыДня.До)
            И (ИнтервалыРабот.До >= ИнтервалыДня.От)
            И (ИнтервалыРабот.До < ИнтервалыДня.До)

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

Ограничения платформы 1С и оптимизация запросов

При работе с интервалами времени в запросах 1С важно учитывать технические особенности платформы:

  1. Отсутствие оконных функций: В языке запросов 1С нет функций LEAD, LAG и ROW_NUMBER() OVER (...). Определение следующей границы отрезка через соединение по неравенству (ОсьВремени.Момент < Позже.Момент) имеет вычислительную сложность O(N²). На выборках более нескольких тысяч строк запрос может замедляться и перегружать временные таблицы базы данных.
  2. Обязательная индексация: Чтобы соединение по неравенству выполнялось максимально эффективно, временные таблицы с контрольными точками обязательно должны содержать директиву индексирования, например: ИНДЕКСИРОВАТЬ ПО Момент.
  3. Модель полуинтервалов: Поле типа Дата в 1С дискретно с точностью до секунды. Если границы отрезков заданы замкнутыми (например, 08:00:00 – 12:00:00 и 12:00:00 – 17:00:00), то момент 12:00:00 попадает сразу в оба интервала, создавая ложные наложения длиной в одну секунду. Чтобы исключить эту проблему, стройте логику по модели полуинтервалов [Начало, Окончание), где правая граница считается не включенной в период.

Альтернативные варианты решения

Если объем данных велик (десятки и сотни тысяч интервалов), вычисление пересечений исключительно запросом может оказаться неоптимальным. Разберем альтернативные инструменты:

  1. Алгоритм сканирующей прямой в памяти (Scanline): Выгружаем точки начал и окончаний в объект ТаблицаЗначений или массив структур, сортируем их за O(N log N) и в одном линейном цикле обхода формируем результирующий список непрерывных интервалов. На стороне сервера 1С такая обработка в оперативной памяти занимает доли секунды.
  2. Механизм вытеснения регистров расчета: Если решается задача учета времени сотрудников или начисления оплаты, целесообразно использовать штатные объекты платформы — Планы видов расчета и Регистры расчета с поддержкой периода действия. Платформа 1С автоматически разделяет периоды действия с учетом графиков работы и правил вытеснения без необходимости вручную писать тяжелые запросы.
← На главную