Как найти пересечения периодов документов в таблице 1С

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

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

Математически верное условие пересечения периодов

Прежде чем приступать к написанию кода, давайте разберем логику самого сравнения. Распространенной ошибкой является использование оператора МЕЖДУ (BETWEEN) для проверки вхождения дат. Однако такой подход находит только случаи, когда один период полностью поглощен другим. Для того чтобы найти любое пересечение двух интервалов [Начало1, Конец1] и [Начало2, Конец2], необходимо использовать следующее условие:

Начало1 <= Конец2 И Начало2 <= Конец1

Рассмотрим, почему это работает. Данное условие покрывает все возможные варианты наложения:

  1. Частичное наложение (один период начинается внутри другого).
  2. Полное вхождение (один период находится внутри другого).
  3. Полное совпадение дат начала и окончания.
  4. Наложение в один день (если границы включительны), что важно учитывать, когда выполняется перенос среднего заработка.

Решение задачи с помощью запроса

Использование механизма запросов в 1С является наиболее предпочтительным, так как СУБД выполняет такие операции значительно быстрее, чем перебор в цикле. Основная идея заключается в том, чтобы соединить таблицу саму с собой по условию пересечения периодов. Проанализируем пример такого запроса:


ВЫБРАТЬ
    Т1.Ссылка КАК Документ1,
    Т2.Ссылка КАК Документ2,
    Т1.ДатаНачала КАК Начало1,
    Т1.ДатаОкончания КАК Конец1,
    Т2.ДатаНачала КАК Начало2,
    Т2.ДатаОкончания КАК Конец2
ПОМЕСТИТЬ ВТ_Данные
ИЗ
    &ИсходнаяТаблица КАК Т1;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Т1.Документ1,
    Т1.Документ2
ИЗ
    ВТ_Данные КАК Т1
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Данные КАК Т2
        ПО Т1.Начало1 <= Т2.Конец2
            И Т1.Конец1 >= Т2.Начало2
            И Т1.Документ1 < Т2.Документ2

Разберем важный нюанс: в условии связи мы использовали Т1.Документ1 < Т2.Документ2. Если написать просто <> (не равно), запрос выдаст каждое пересечение дважды: сначала (Документ А - Документ Б), а затем (Документ Б - Документ А). Использование знака "меньше" позволяет отсечь зеркальные дубли и исключить сравнение документа самого с собой.

Оптимизация при работе с большими данными: Алгоритм «Сканирующей линии»

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

Выясним алгоритм действий:

  1. Создаем вспомогательную таблицу со следующими колонками: Дата, ТипТочки (Начало = +1, Конец = -1), Документ.
  2. Сортируем таблицу по колонке Дата.
  3. Обходим таблицу одним циклом.
  4. Если встречаем "Начало", проверяем список "активных" документов. Если в списке уже кто-то есть — это пересечение. Добавляем текущий документ в список.
  5. Если встречаем "Конец", удаляем документ из списка активных.

Этот метод работает за время O(n log n), что на огромных массивах данных гораздо быстрее, чем O(n²).

Использование соответствий для ускорения поиска в цикле

Если мы работаем с данными "в памяти" (например, проверяем табличную часть перед записью объекта) и не хотим обращаться к запросам, можно воспользоваться коллекцией Соответствие или Структура. Это эффективнее, чем вложенный цикл Для каждого.

Проанализируем пример кода, который проверяет пересечения по дням:


Пересечения = Новый СписокЗначений;
ТаблицаДней = Новый Соответствие; // Ключ - Дата, Значение - Документ

Для Каждого СтрокаДок Из ТаблицаДокументов Цикл
    ТекущаяДата = НачалоДня(СтрокаДок.ДатаНачала);
    ДатаКонца = КонецДня(СтрокаДок.ДатаОкончания);
    
    Пока ТекущаяДата <= ДатаКонца Цикл
        НайденныйДокумент = ТаблицаДней.Получить(ТекущаяДата);
        
        Если НайденныйДокумент <> Неопределено Тогда
            Сообщить("Найдено пересечение: " + СтрокаДок.Ссылка + " и " + НайденныйДокумент);
        Иначе
            ТаблицаДней.Вставить(ТекущаяДата, СтрокаДок.Ссылка);
        КонецЕсли;
        
        ТекущаяДата = ТекущаяДата + 86400; // Добавляем сутки
    КонецЦикла;
КонецЦикла;

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

Учет специфики времени и работы в выходные в 1С

При проверке пересечений крайне важно следить за тем, как хранятся даты. В 1С тип Дата всегда содержит время. Если в вашей системе документы "Отпуск" записываются с временем 00:00:00, то период с 01.01 по 01.01 по факту может длиться всего одну секунду.

Рассмотрим рекомендации по работе с временем:

Подведем итоги

Мы рассмотрели три способа поиска пересечений: через математическое условие в запросе (самый универсальный), через соответствие дат (быстрый для оперативной проверки ТЧ) и через сканирующую линию (для высоконагруженных систем). Выбор метода зависит от объема данных и контекста выполнения кода. Помните о правильной обработке границ периодов и всегда проверяйте зеркальные дубли в результатах поиска.

← На главную