Как в запросе 1С вычесть одни периоды из других?

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

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

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

Алгоритм нарезки интервалов по контрольным точкам

Основная идея решения заключается в разделении исходного временного диапазона на непрерывные элементарные отрезки («срезы»), границы которых определяются точками изменения состояний.

Разберем логику построения срезов по шагам:

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

Пример реализации в запросе 1С

Посмотрим на практическую реализацию задачи. Допустим, у нас есть таблица базовых периодов ВТТаблица1 (например, с 01.01.2016 по 31.12.2016) и таблица исключаемых периодов ВТТаблица2 (с 15.01.2016 по 05.02.2016 и с 01.08.2016 по 10.08.2016).

Ниже представлен полный текст запроса, реализующий нарезку и фильтрацию:


ВЫБРАТЬ
    &НачалоТ1 КАК Начало,
    &КонецТ1 КАК Конец
ПОМЕСТИТЬ ВТТаблица1
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    &Начало1Т2 КАК Начало,
    &Конец1Т2 КАК Конец
ПОМЕСТИТЬ ВТТаблица2

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

ВЫБРАТЬ
    &Начало2Т2,
    &Конец2Т2
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТТаблица1.Начало КАК Период
ПОМЕСТИТЬ ВТТочкиИзменения
ИЗ
    ВТТаблица1 КАК ВТТаблица1

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    ВТТаблица2.Начало
ИЗ
    ВТТаблица2 КАК ВТТаблица2

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(ВТТаблица1.Конец, ДЕНЬ, 1)
ИЗ
    ВТТаблица1 КАК ВТТаблица1

ОБЪЕДИНИТЬ

ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(ВТТаблица2.Конец, ДЕНЬ, 1)
ИЗ
    ВТТаблица2 КАК ВТТаблица2
;

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Срезы.ДатаНачала,
    Срезы.ДатаКонец
ИЗ
    ВТСрезыПериодов КАК Срезы
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТТаблица1 КАК БазовыеПериоды
        ПО Срезы.ДатаНачала >= БазовыеПериоды.Начало
            И Срезы.ДатаКонец <= БазовыеПериоды.Конец
        ЛЕВОЕ СОЕДИНЕНИЕ ВТТаблица2 КАК ИсключаемыеПериоды
        ПО Срезы.ДатаНачала <= ИсключаемыеПериоды.Конец
            И Срезы.ДатаКонец >= ИсключаемыеПериоды.Начало
ГДЕ
    ИсключаемыеПериоды.Начало ЕСТЬ NULL

Важные нюансы и оптимизация алгоритма

Разберем ключевые детали, на которые необходимо обратить внимание при использовании этого подхода:

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

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

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

  1. Оконные функции (начиная с платформы 8.3.20): вместо соединения таблицы с самой собой для получения следующей даты можно использовать функцию LEAD(Период, 1) OVER (ORDER BY Период). Это исключает декартово произведение и работает значительно быстрее.
  2. Разворачивание в календарную сетку (дискретизация): базовый период соединяется с производственным календарем или периодическим регистром по дням, левым соединением исключаются вычитаемые дни, после чего оставшиеся непрерывные дни сворачиваются обратно в периоды.
  3. Обработка на встроенном языке 1С: если вычисление выполняется не внутри сложного отчета СКД, нарезку отрезков быстрее и проще выполнить кодом модуля (через ТаблицаЗначений или массив структур), последовательно обходя отсортированные периоды в одном цикле.
← На главную