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

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

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

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

Шаг 1: Генерация временной таблицы дат

Для того чтобы получить данные на каждый день, нам сначала нужна «опорная» таблица, содержащая все даты целевого периода. В 1С нет готовой функции, которая возвращает таблицу дат, поэтому мы сформируем её с помощью соединения нескольких таблиц с цифрами. Это классический прием, позволяющий получить последовательность чисел, которую затем можно преобразовать в даты.

Рассмотрим пример формирования таблицы дат на 1000 дней вперед от даты начала:


ВЫБРАТЬ 0 КАК Цифра ПОМЕСТИТЬ ВТ_Цифры ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4 
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&ДатаНачала, ДЕНЬ, Единицы.Цифра + Десятки.Цифра * 10 + Сотни.Цифра * 100), ДЕНЬ) КАК ПериодДень
ПОМЕСТИТЬ ВТ_Календарь
ИЗ
    ВТ_Цифры КАК Единицы
        КРОСС СОЕДИНЕНИЕ ВТ_Цифры КАК Десятки
        КРОСС СОЕДИНЕНИЕ ВТ_Цифры КАК Сотни
ГДЕ
    ДОБАВИТЬКДАТЕ(&ДатаНачала, ДЕНЬ, Единицы.Цифра + Десятки.Цифра * 10 + Сотни.Цифра * 100) <= &ДатаОкончания;

В данном фрагменте мы используем КРОСС СОЕДИНЕНИЕ (декартово произведение), чтобы получить комбинации цифр от 0 до 999. Условие в секции ГДЕ ограничивает выборку только нужным нам интервалом дат. Это гораздо производительнее, чем передавать готовую таблицу значений из кода 1С в запрос.

Шаг 2: Получение актуального состояния на каждую дату

Теперь, когда у нас есть таблица ВТ_Календарь, нам необходимо соединить её с регистром сведений СостояниеФизическихЛиц. Основная проблема здесь — получить именно ту запись, которая является «последней» для каждой конкретной даты. Виртуальная таблица СрезПоследних не подходит напрямую, так как она не умеет принимать поле из другой таблицы в качестве параметра периода.

Проанализируем метод имитации среза последних через подзапрос. Мы ищем максимальную дату записи в регистре, которая меньше или равна нашей текущей дате из календаря:


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

Важный момент: если ваш регистр содержит реквизит ДатаОкончания, его необходимо учитывать. В противном случае статус будет «тянуться» бесконечно, пока не появится новая запись. Если же статус ограничен во времени, условие соединения должно быть дополнено проверкой: Календарь.ПериодДень МЕЖДУ Регистр.Период И Регистр.ДатаОкончания.

Шаг 3: Обработка вытеснения и приоритетов

Ситуация, описанная автором (Простой вытесняется Командировкой), указывает на то, что в один и тот же день для сотрудника могут действовать две записи. В типовых конфигурациях вроде ЗУП 3.1 эта проблема решается с помощью интервальных регистров, где периоды уже «нарезаны» без пересечений при записи документа. Если мы работаем с обычным регистром, нам нужно определить приоритет.

Рассмотрим, как можно применить логику выбора приоритетного состояния. Мы можем добавить к статусам числовой приоритет (например, Командировка = 2, Простой = 1). Тогда финальный запрос будет выглядеть так:


ВЫБРАТЬ
    Календарь.ПериодДень,
    Состояния.Статус
ИЗ
    ВТ_Календарь КАК Календарь
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СостояниеФизическихЛиц КАК Состояния
        ПО Состояния.ФизическоеЛицо = &Сотрудник
        И Календарь.ПериодДень МЕЖДУ Состояния.Период И Состояния.ДатаОкончания
ГДЕ
    // Выбираем только одну запись с максимальным приоритетом или самой поздней датой начала
    Состояния.Период = (
        ВЫБРАТЬ МАКСИМУМ(Внутр.Период)
        ИЗ РегистрСведений.СостояниеФизическихЛиц КАК Внутр
        ГДЕ Внутр.ФизическоеЛицо = &Сотрудник
        И Календарь.ПериодДень МЕЖДУ Внутр.Период И Внутр.ДатаОкончания
    )

Проанализируем ситуацию, если записи вносятся «в пределах секунды». Использование МАКСИМУМ(Период) в подзапросе гарантирует, что если командировка была введена позже простоя и их периоды пересекаются, запрос выберет именно её. Это и реализует механизм «вытеснения» в простейшем виде.

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

При работе с большими объемами данных (много сотрудников и длинные периоды) такие запросы могут выполняться медленно из-за большого количества соединений. Выясним причины и способы оптимизации:

  1. Индексация: Убедитесь, что измерения ФизическоеЛицо и Период проиндексированы. Если используется ДатаОкончания, желательно также включить её в индекс.
  2. Временные таблицы: Всегда разбивайте сложный запрос на этапы с помещением промежуточных итогов в ВТ. Это помогает планировщику SQL-сервера построить оптимальный план выполнения.
  3. Использование Производственного календаря: Вместо генерации цифр можно использовать физическую таблицу производственного календаря, если он заполнен в системе. Это упростит текст запроса и сделает его более понятным.

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

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

← На главную