Задачи, связанные с анализом временных интервалов, встречаются в практике разработчика 1С повсеместно: от проверки наложений в графиках работы до контроля пересечений периодов действия цен или скидок. Несмотря на кажущуюся простоту, реализация такого алгоритма в языке запросов таит в себе множество нюансов — от логических ошибок в условиях до критического падения производительности. В этой статье мы подробно разберем, как составить корректный запрос для выборки пересекающихся периодов, избежав типичных «граблей».
Предположим, у нас есть таблица (или временная таблица) Периоды с полями От (дата начала) и До (дата окончания). Нам необходимо определить пары пересекающихся интервалов или подсчитать общее количество таких наложений. Рассмотрим типичный подход с использованием левого соединения таблицы самой с собой, предложенный в начале обсуждения:
ВЫБРАТЬ
КОЛИЧЕСТВО(Лево.От) КАК От
ИЗ
Периоды КАК Лево
ЛЕВОЕ СОЕДИНЕНИЕ Периоды КАК Право
ПО (НЕ Лево.От = Право.От)
И (НЕ Лево.До = Право.До)
И Лево.От < Право.До
И Лево.До > Право.От
И Лево.От >= Право.От
ГДЕ
НЕ Право.От ЕСТЬ NULL
Проанализируем это решение. Автор пытается отсечь соединение записи самой с собой через проверку равенства дат, но это не сработает, если в системе могут существовать два разных, но абсолютно идентичных по времени периода. Кроме того, условие Лево.От >= Право.От в сочетании с другими фильтрами может привести к потере части данных или некорректному подсчету в зависимости от того, ищем мы пары или общее количество задействованных строк.
Разберем математически верное условие пересечения двух интервалов [Начало1, Конец1] и [Начало2, Конец2]. Чтобы два отрезка имели хотя бы одну общую точку, должно выполняться правило: каждый интервал должен начинаться до того, как закончится другой.
На языке запросов 1С для закрытых интервалов (включающих границы) это выглядит так:
Начало1 <= Конец2 И Конец1 >= Начало2
Если же ваша бизнес-логика подразумевает полуоткрытые интервалы (когда момент окончания одного периода является моментом начала другого, но они не считаются пересекающимися), следует использовать строгие неравенства:
Начало1 < Конец2 И Конец1 > Начало2
Выясним причину важности этого различия. В 1С дата часто включает время до секунды. Если один период заканчивается в 23:59:59, а другой начинается в 00:00:00 следующего дня, использование <= и >= не покажет пересечения, что верно. Однако, если границы установлены на одну и ту же секунду, выбор условия определит результат.
При использовании SELF JOIN (соединения таблицы самой с собой) мы неизбежно сталкиваемся с тем, что система находит пересечение Периода А с Периодом Б, а затем — Периода Б с Периодом А. В итоге результат удваивается. Кроме того, период всегда пересекается сам с собой.
Чтобы избежать этого, проанализируем ситуацию: нам нужно сравнивать только разные записи и только в одном направлении. Самый эффективный способ сделать это — использовать уникальный идентификатор записи (например, поле Ссылка или уникальный номер строки во временной таблице НомерСтроки).
Рассмотрим правильный вариант условия связи:
ВЫБРАТЬ
Лево.Период КАК Период1,
Право.Период КАК Период2
ИЗ
ВТ_Периоды КАК Лево
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Периоды КАК Право
ПО Лево.Ссылка < Право.Ссылка
И Лево.ДатаНачала <= Право.ДатаОкончания
И Лево.ДатаОкончания >= Право.ДатаНачала
Важный момент: Использование оператора < (меньше) для уникальных идентификаторов решает сразу две задачи:
В реальных задачах 1С дата окончания периода часто может быть не заполнена (значение 0001-01-01), что означает «по настоящее время» или «бесконечность». Если не учесть это в запросе, пустая дата будет трактоваться как самое начало временной шкалы, и логика пересечений полностью нарушится.
Для корректной работы мы должны «нормализовать» пустые даты, используя функцию ВЫБОР или ЕСТЬNULL. Посмотрим на примере, как подставить максимально возможную дату:
ВЫБРАТЬ
Т.Ссылка,
ВЫБОР
КОГДА Т.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1)
ТОГДА ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)
ИНАЧЕ Т.ДатаОкончания
КОНЕЦ КАК ДатаОкончанияНорм
ПОМЕСТИТЬ ВТ_ПодготовленныеДанные
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК Т
Только после такой подготовки можно выполнять соединение для поиска пересечений. В противном случае интервалы с пустой датой окончания просто «выпадут» из корректного анализа.
Если в вашей таблице десятки или сотни тысяч записей, обычное соединение O(N²) приведет к зависанию системы. В таких случаях мы используем алгоритм «заметающей прямой» (Sweep-line). Его суть в том, чтобы превратить периоды в последовательность событий (точек) на временной оси.
Разберем по шагам, как реализовать это в запросе:
+1) и момент окончания (со значением -1).Посмотрим на пример кода для поиска максимального количества пересечений (пиковой нагрузки):
ВЫБРАТЬ
Т.ДатаНачала КАК ТочкаВремени,
1 КАК Изменение
ПОМЕСТИТЬ ВТ_События
ИЗ
ВТ_Периоды КАК Т
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Т.ДатаОкончания,
-1
ИЗ
ВТ_Периоды КАК Т;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Т1.ТочкаВремени,
СУММА(Т2.Изменение) КАК ТекущееКоличество
ИЗ
ВТ_События КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_События КАК Т2
ПО Т1.ТочкаВремени >= Т2.ТочкаВремени
ГРУППИРОВАТЬ ПО
Т1.ТочкаВремени
Этот метод позволяет найти интервалы времени, в которых происходит наложение, гораздо быстрее, чем полное перекрестное сравнение всех периодов друг с другом.
Подведем итоги нашего анализа. Чтобы эффективно и правильно работать с пересекающимися периодами в 1С, придерживайтесь следующих правил:
Начало1 <= Конец2 И Конец1 >= Начало2 как стандарт де-факто для поиска пересечений.ВЫБРАТЬ ПЕРВЫЕ 1 для ускорения работы СУБД.Используя эти методы, вы сможете создавать надежные и быстрые механизмы контроля временных интервалов в ваших конфигурациях, обеспечивая целостность данных и комфортную работу пользователей.