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

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

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

Проблема стандартных методов

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

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

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

Решение: Объединение среза последних и физической таблицы

Разберем основной алгоритм решения задачи. Чтобы получить полную картину, мы должны выполнить операцию ОБЪЕДИНИТЬ ВСЕ для двух разных запросов.

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

Шаг 2: Получение движений внутри периода. Мы выбираем все записи из физической таблицы регистра, которые были сделаны после начала периода и до его окончания.

Рассмотрим пример кода запроса:


ВЫБРАТЬ
    СрезНаНачало.Сотрудник КАК Сотрудник,
    СрезНаНачало.ПодразделениеОрганизации КАК ПодразделениеОрганизации,
    &НачалоПериода КАК Период
ИЗ
    РегистрСведений.РаботникиОрганизаций.СрезПоследних(&НачалоПериода) КАК СрезНаНачало

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

ВЫБРАТЬ
    ДвиженияЗаПериод.Сотрудник,
    ДвиженияЗаПериод.ПодразделениеОрганизации,
    ДвиженияЗаПериод.Период
ИЗ
    РегистрСведений.РаботникиОрганизаций КАК ДвиженияЗаПериод
ГДЕ
    ДвиженияЗаПериод.Период > &НачалоПериода
    И ДвиженияЗаПериод.Период <= &КонецПериода

Важный нюанс: Границы периодов и дублирование

Проанализируем критически важный момент — условие фильтрации по времени во второй части запроса. Если мы напишем Период МЕЖДУ &НачалоПериода И &КонецПериода, то запись, сделанная ровно в 00:00:00 первого дня периода, попадет в выборку дважды: один раз из виртуальной таблицы СрезПоследних и второй раз из физической таблицы.

Чтобы избежать дублей и обеспечить математическую точность интервалов, рекомендуется использовать строгое неравенство для нижней границы во втором запросе: ДвиженияЗаПериод.Период > &НачалоПериода. В этом случае СрезПоследних забирает всё, что актуально на "ноль часов ноль минут", а физическая таблица — всё, что произошло строго после этого мгновения.

Определение интервалов (Дата начала и Дата окончания)

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

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


// Помещаем объединенные данные во временную таблицу
ВЫБРАТЬ
    Т.Сотрудник,
    Т.ПодразделениеОрганизации,
    Т.Период
ПОМЕСТИТЬ ВТ_ВсеСостояния
ИЗ
    (...) КАК Т; // Здесь наш UNION из предыдущего примера

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

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

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

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

  1. Индексация: Обязательно индексируйте временную таблицу ВТ_ВсеСостояния по полям Сотрудник и Период. Это на порядок ускорит операцию соединения при поиске следующей даты.
  2. Фильтрация на ранних этапах: Старайтесь ограничивать выборку по конкретным подразделениям или списку сотрудников сразу в обоих частях объединения (UNION), чтобы не обрабатывать лишние данные.
  3. Периодичность регистра: Помните о настройках регистра. Если периодичность установлена «В пределах дня», время в параметрах &НачалоПериода должно быть приведено к началу дня, иначе возможны логические ошибки при сравнении моментов времени.

Использование временных таблиц для связей с другими данными

Когда вы получили таблицу состояний, её удобно использовать для соединения с другими данными. Например, если вам нужно рассчитать зарплату пропорционально времени, проведенному в каждом подразделении, вы соединяете вашу таблицу интервалов с таблицей фактически отработанного времени (из регистров расчета).

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

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

← На главную