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

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

Задачи, связанные с анализом временных интервалов, встречаются в практике разработчика 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. Исключает соединение записи самой с собой (так как Ссылка не может быть меньше самой себя).
  2. Исключает зеркальные дубли (из пары А-Б и Б-А останется только та, где Ссылка первой записи меньше Ссылки второй).

Обработка пустых дат (бесконечных периодов)

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

Для корректной работы мы должны «нормализовать» пустые даты, используя функцию ВЫБОР или ЕСТЬNULL. Посмотрим на примере, как подставить максимально возможную дату:


ВЫБРАТЬ
    Т.Ссылка,
    ВЫБОР
        КОГДА Т.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1)
            ТОГДА ДАТАВРЕМЯ(3999, 12, 31, 23, 59, 59)
        ИНАЧЕ Т.ДатаОкончания
    КОНЕЦ КАК ДатаОкончанияНорм
ПОМЕСТИТЬ ВТ_ПодготовленныеДанные
ИЗ
    РегистрСведений.ЦеныНоменклатуры КАК Т

Только после такой подготовки можно выполнять соединение для поиска пересечений. В противном случае интервалы с пустой датой окончания просто «выпадут» из корректного анализа.

Оптимизация производительности: Метод заметающей прямой

Если в вашей таблице десятки или сотни тысяч записей, обычное соединение O(N²) приведет к зависанию системы. В таких случаях мы используем алгоритм «заметающей прямой» (Sweep-line). Его суть в том, чтобы превратить периоды в последовательность событий (точек) на временной оси.

Разберем по шагам, как реализовать это в запросе:

  1. Для каждого периода создаем две записи: момент начала (со значением +1) и момент окончания (со значением -1).
  2. Объединяем эти записи в одну таблицу.
  3. Сортируем по времени и вычисляем нарастающий итог.

Посмотрим на пример кода для поиска максимального количества пересечений (пиковой нагрузки):


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

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

ВЫБРАТЬ
    Т.ДатаОкончания,
    -1
ИЗ
    ВТ_Периоды КАК Т;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Т1.ТочкаВремени,
    СУММА(Т2.Изменение) КАК ТекущееКоличество
ИЗ
    ВТ_События КАК Т1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_События КАК Т2
        ПО Т1.ТочкаВремени >= Т2.ТочкаВремени
ГРУППИРОВАТЬ ПО
    Т1.ТочкаВремени

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

Резюме и практические советы

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

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

← На главную