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

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

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

Проблема использования стандартного Среза последних

Когда мы используем РегистрСведений.ТР_ИсторияСостояний.СрезПоследних, мы получаем состояние объекта на конкретный момент. Если в параметрах виртуальной таблицы указать период, система вернет последнюю запись, сделанную до этого момента. Однако для построения отчета за интервал (например, с 1-го по 30-е число) нам недостаточно одной точки. Нам нужно знать:

  1. В каком состоянии задача вошла в этот период (состояние на начало периода).
  2. Все изменения, которые произошли внутри периода.
  3. Как долго длилось каждое состояние.

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

Способ №1: Метод самосоединения таблицы для формирования интервалов

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

Нам необходимо соединить таблицу ТР_ИсторияСостояний (назовем ее Т1) с этой же таблицей (назовем ее Т2) по условию, что Т2 содержит следующую по времени запись для той же задачи. Посмотрим на пример реализации такого запроса:


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

В данном запросе мы используем ЛЕВОЕ СОЕДИНЕНИЕ, чтобы не потерять самую последнюю запись, у которой еще нет «следующей» в будущем. Функция МИНИМУМ позволяет найти именно тот момент времени, когда состояние изменилось в следующий раз. Если же следующей записи нет, мы подставляем конец периода отчета (параметр &ДатаОкончания) с помощью функции ЕСТЬNULL.

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


ВЫБРАТЬ
    ВТ_Интервалы.Задача,
    ВТ_Интервалы.Состояние,
    ВТ_Интервалы.ДатаНачала,
    ДОБАВИТЬКДАТЕ(ВТ_Интервалы.ДатаОкончания, СЕКУНДА, -1) КАК ДатаОкончанияСкорректированная
ИЗ
    ВТ_Интервалы КАК ВТ_Интервалы

Способ №2: Развертка периода по дням

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

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

Рассмотрим пример логики, если мы работаем в СКД:

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

Оптимизация и очистка данных

При работе с историей состояний часто возникают «дубли» — записи в регистре, которые были созданы, но фактически не изменили состояние (например, статус «В работе» был записан дважды подряд разными документами). Чтобы отчет был чистым, проанализируем способ удаления таких избыточных данных.

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


ВЫБРАТЬ
    История.Задача,
    История.Период,
    История.Состояние
ИЗ
    РегистрСведений.ТР_ИсторияСостояний КАК История
ГДЕ
    (История.Задача, История.Период) В (
        ВЫБРАТЬ
            Т_Внутр.Задача,
            МИНИМУМ(Т_Внутр.Период)
        ИЗ
            РегистрСведений.ТР_ИсторияСостояний КАК Т_Внутр
        СГРУППИРОВАТЬ ПО
            Т_Внутр.Задача,
            Т_Внутр.Состояние
    )

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

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

← На главную