Как правильно составить условие пересечения периодов в запросе 1С

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

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

В этой статье мы подробно разберем, как составить универсальное и максимально производительное условие для запроса, которое учтет все нюансы пересечения дат.

Анализ проблемы: почему стандартное МЕЖДУ не подходит

Рассмотрим ситуацию, описанную пользователем. Есть выбранный период отчета: 01.05.2018 — 31.05.2018. Нам нужно, чтобы в отчет попали следующие записи:

  1. Болезнь 1: 01.04.2018 — 03.05.2018 (началась до периода, закончилась внутри).
  2. Болезнь 2: 28.05.2018 — 06.06.2018 (началась внутри, закончилась после).
  3. Болезнь 3: 04.05.2018 — 12.05.2018 (полностью внутри периода).
  4. Болезнь 4: 01.01.2018 — 31.10.2018 (началась задолго «до» и закончится «после», полностью поглощая май).

Многие начинающие разработчики пытаются использовать оператор МЕЖДУ (или BETWEEN), прописывая условие так:


ГДЕ 
    НачалоБолезни МЕЖДУ &НачалоПериода И &КонецПериода 
    ИЛИ ОкончаниеБолезни МЕЖДУ &НачалоПериода И &КонецПериода

Проанализируем этот код. Он корректно выберет первые три примера. Однако Болезнь 4 в отчет не попадет! Почему? Потому что ни дата начала (01.01), ни дата окончания (31.10) не входят в майский интервал. Хотя фактически сотрудник в мае болел и отсутствовал на работе. Именно здесь кроется главная ловушка логики «вхождения» границ.

Универсальное решение: условие «перекрестного» сравнения

Самое элегантное, математически выверенное и быстрое решение заключается в использовании всего двух сравнений. Чтобы два интервала (ИнтервалА и ИнтервалБ) пересекались, должно соблюдаться простое правило: начало первого периода должно быть не позднее конца второго, а конец первого — не ранее начала второго.

В контексте нашего запроса это выглядит следующим образом:


ГДЕ 
    НачалоБолезни <= &КонецПериода 
    И ОкончаниеБолезни >= &НачалоПериода

Разберем, почему это работает для всех наших случаев:

Это условие охватывает абсолютно все варианты пересечения, включая те, где один период полностью поглощает другой. Оно максимально эффективно для SQL-сервера, так как использует простые операции сравнения, по которым легко строятся индексы.

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

В реальных базах данных 1С часто встречаются ситуации, когда дата окончания события не заполнена (например, сотрудник ушел в декрет и дата выхода еще не определена). В системе 1С «пустая» дата соответствует значению 01.01.0001.

Если мы применим наше условие ОкончаниеБолезни >= &НачалоПериода к пустой дате, запись будет отсечена, так как 0001 год явно меньше 2018-го. Чтобы избежать потери данных, проанализируем ситуацию и добавим проверку на пустую дату:


ГДЕ 
    НачалоБолезни <= &КонецПериода 
    И (ОкончаниеБолезни >= &НачалоПериода ИЛИ ОкончаниеБолезни = ДАТАВРЕМЯ(1, 1, 1))

Таким образом, мы гарантируем, что «открытые» записи также попадут в наш отчет.

Важность учета времени и функции КОНЕЦПЕРИОДА

Частой ошибкой является игнорирование времени в датах. Если пользователь в параметрах отчета указал дату окончания «31.05.2018», 1С по умолчанию воспримет это как 31.05.2018 00:00:00. Если же болезнь сотрудника началась 31 мая в 10 утра, условие НачалоБолезни <= &КонецПериода может не сработать (так как 10:00:00 больше, чем 00:00:00).

Рассмотрим правильный подход к передаче параметров. Мы всегда должны приводить правую границу периода к концу дня. В языке запросов это делается так:


ГДЕ 
    НачалоБолезни <= КОНЕЦПЕРИОДА(&КонецПериода, ДЕНЬ) 
    И (ОкончаниеБолезни >= &НачалоПериода ИЛИ ОкончаниеБолезни = ДАТАВРЕМЯ(1, 1, 1))

Либо, что еще правильнее, подготавливать значения параметров в коде на встроенном языке перед выполнением запроса, используя функцию КонецДня().

Как рассчитать количество дней болезни именно внутри периода

Иногда просто отобрать записи недостаточно — нужно вычислить, сколько дней сотрудник болел именно в выбранном месяце. Например, для Болезни 1 (01.04 — 03.05) в майский отчет должно попасть только 3 дня.

Для этого нам нужно найти «фактическое» начало и «фактический» конец внутри нашего интервала. Воспользуемся конструкцией ВЫБОР для определения экстремумов:


ВЫБРАТЬ
    Сотрудник,
    // Вычисляем фактическую дату начала внутри периода
    ВЫБОР 
        КОГДА НачалоБолезни < &НачалоПериода ТОГДА &НачалоПериода 
        ИНАЧЕ НачалоБолезни 
    КОНЕЦ КАК ФактическоеНачало,
    
    // Вычисляем фактическую дату окончания внутри периода
    ВЫБОР 
        КОГДА ОкончаниеБолезни > &КонецПериода ИЛИ ОкончаниеБолезни = ДАТАВРЕМЯ(1, 1, 1) ТОГДА &КонецПериода 
        ИНАЧЕ ОкончаниеБолезни 
    КОНЕЦ КАК ФактическоеОкончание
ПОМЕСТИТЬ ВТ_СкорректированныеДаты
ИЗ
    РегистрСведений.СостоянияСотрудников
ГДЕ
    НачалоБолезни <= &КонецПериода 
    И (ОкончаниеБолезни >= &НачалоПериода ИЛИ ОкончаниеБолезни = ДАТАВРЕМЯ(1, 1, 1));

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Сотрудник,
    РАЗНОСТЬДАТ(ФактическоеНачало, ФактическоеОкончание, ДЕНЬ) + 1 КАК КоличествоДнейВПериоде
ИЗ
    ВТ_СкорректированныеДаты

Обратите внимание на + 1 в функции РАЗНОСТЬДАТ. Это необходимо, так как если сотрудник болел с 1 по 1 число, разность дат вернет 0, хотя фактически это 1 день болезни.

Оптимизация производительности: индексы

Проанализируем работу запроса на больших объемах данных. Если в регистре сведений миллионы записей, условие по двум полям НачалоБолезни и ОкончаниеБолезни может привести к полному сканированию таблицы (Table Scan), что крайне медленно.

Чтобы отчет работал быстро, убедитесь, что:

  1. Поля дат являются индексируемыми. В свойствах реквизита в конфигураторе должно стоять значение Индексировать.
  2. Если вы используете периодический регистр сведений, стандартное поле Период индексируется платформой автоматически.
  3. В идеале, первым в условии должно идти то поле, которое наиболее сильно ограничивает выборку.

Использование в Системе Компоновки Данных (СКД)

При работе с СКД часто возникает соблазн использовать стандартные параметры НачалоПериода и КонецПериода. Однако помните: если вы используете механизм автозаполнения, СКД может попытаться самостоятельно наложить условия на виртуальные таблицы, что не всегда подходит для логики пересечения интервалов.

Для надежности при разработке сложных отчетов по периодам рекомендуется:

Подводя итог, можно сказать, что правильная работа с датами в 1С требует понимания того, как пересекаются отрезки на временной оси. Использование условия Начало1 <= Конец2 И Конец1 >= Начало2 — это «золотой стандарт» разработки, который убережет вас от ошибок и обеспечит корректность данных в любых отчетах.

← На главную