В практике разработки на платформе 1С нередко возникает задача вытеснения или вычитания периодов: когда есть базовый интервал (или несколько интервалов) и набор периодов-исключений, которые требуется «вырезать» из базового диапазона. Если в конфигурациях на базе подсистемы расчета зарплаты этот механизм встроен в регистры расчета, то в произвольном запросе логику вычитания отрезков времени приходится реализовывать самостоятельно.
Рассмотрим подробно алгоритм решения этой задачи с помощью языка запросов 1С, проанализируем примеры кода, а также разберем возможные краевые случаи и альтернативные варианты реализации.
Основная идея решения заключается в разделении исходного временного диапазона на непрерывные элементарные отрезки («срезы»), границы которых определяются точками изменения состояний.
Разберем логику построения срезов по шагам:
ДОБАВИТЬКДАТЕ(Конец, ДЕНЬ, 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)) либо переходить на работу с полуинтервалами вида [ДатаНачала, ДатаОкончания).ВТТочки.Период < СледующиеПериоды.Период выполняет декартово произведение строк. Если точек переключения много (сотни или тысячи), такой запрос создаст высокую нагрузку на СУБД. В этом случае во временных таблицах точек изменения обязательно задавать индексацию: ИНДЕКСИРОВАТЬ ПО Период.В зависимости от версии платформы 1С и архитектурных требований задачи можно применить другие методы:
LEAD(Период, 1) OVER (ORDER BY Период). Это исключает декартово произведение и работает значительно быстрее.ТаблицаЗначений или массив структур), последовательно обходя отсортированные периоды в одном цикле.