Как доработать запрос для фильтрации по текущему статусу и расчета времени обработки в 1С 8.2

Программист 1С v8.3 (Обычные формы) Управленческий учет
← На главную

При разработке отчетов по движению кандидатов или бизнес-процессам часто возникает классическая проблема: отчет выводит записи, которые уже сменили свой статус, хотя пользователь установил фильтр на конкретное состояние. Рассмотрим ситуацию на примере регистра сведений ТекущаяРаботаПоЗаявкамКандидатов. Основная сложность здесь заключается в том, что обычный фильтр в секции ГДЕ по полю Состояние ищет наличие этого состояния во всей истории изменений, а нам нужно анализировать только последнее актуальное значение.

В этой статье мы разберем, как правильно построить запрос в условиях платформы 1С 8.2, где отсутствует современная функция АВТОНОМЕРЗАПИСИ(), и как корректно рассчитать время нахождения в каждом статусе.

Причина некорректной фильтрации

Разберем ситуацию, когда кандидат Бирюкова Анна сначала имела статус "На рассмотрении", а затем перешла в статус "Принята". Если мы просто пишем условие ГДЕ Состояние = &Статус, система находит в истории первую запись Бирюковой и включает ее в отчет. Нам же необходимо сначала определить текущее состояние кандидата и только потом применять фильтр.

Проанализируем типичную ошибку в соединениях. Если вы используете ЛЕВОЕ СОЕДИНЕНИЕ с таблицей статусов, но в секции ГДЕ ставите условие на поле из этой правой таблицы (например, НЕ ТаблицаСтатусов.Кандидат ЕСТЬ NULL), то ваше соединение фактически превращается во внутреннее (Inner Join). Это отсекает данные и часто приводит к неверным результатам при расчете временных интервалов.

Шаг 1: Получение актуального состояния через СрезПоследних

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


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

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

Шаг 2: Расчет времени между сменой статусов в 1С 8.2

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

Проанализируем алгоритм эмуляции нумерации строк:

  1. Выбираем все изменения состояний за период во временную таблицу.
  2. Соединяем таблицу саму с собой по условию Таблица1.Период >= Таблица2.Период.
  3. Группируем по периоду первой таблицы и считаем количество записей во второй — это и будет наш порядковый номер.

Рассмотрим пример кода для подготовки пронумерованной истории:


ВЫБРАТЬ
    Рег.Период КАК Период,
    Рег.ЗаявкаКандидата КАК Кандидат,
    Рег.Состояние КАК Состояние
ПОМЕСТИТЬ ВТ_История
ИЗ
    РегистрСведений.ТекущаяРаботаПоЗаявкамКандидатов КАК Рег
ГДЕ
    Рег.Период МЕЖДУ &ДатаНачало И &ДатаКонец
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ТаблицаДляСвязи.Период) КАК НомерСтроки,
    Таблица.Период КАК Период,
    Таблица.Кандидат КАК Кандидат,
    Таблица.Состояние КАК Состояние
ПОМЕСТИТЬ ВТ_ИсторияСНомерами
ИЗ
    ВТ_История КАК Таблица
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_История КАК ТаблицаДляСвязи
        ПО Таблица.Кандидат = ТаблицаДляСвязи.Кандидат
            И Таблица.Период >= ТаблицаДляСвязи.Период
СГРУППИРОВАТЬ ПО
    Таблица.Кандидат,
    Таблица.Период,
    Таблица.Состояние
;

Шаг 3: Соединение истории и вычисление разницы

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


ВЫБРАТЬ
    ИсторияТекущая.Кандидат КАК Кандидат,
    ИсторияТекущая.Состояние КАК Состояние,
    ИсторияТекущая.Период КАК ДатаНачала,
    ЕСТЬNULL(ИсторияПредыдущая.Период, ИсторияТекущая.Период) КАК ДатаПредыдущая,
    РАЗНОСТЬДАТ(ЕСТЬNULL(ИсторияПредыдущая.Период, ИсторияТекущая.Период), ИсторияТекущая.Период, СЕКУНДА) КАК СекундВСтатусе
ИЗ
    ВТ_ИсторияСНомерами КАК ИсторияТекущая
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИсторияСНомерами КАК ИсторияПредыдущая
        ПО ИсторияТекущая.Кандидат = ИсторияПредыдущая.Кандидат
            И ИсторияТекущая.НомерСтроки = (ИсторияПредыдущая.НомерСтроки + 1)
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_АктуальныеСтатусы КАК Актуальные
        ПО ИсторияТекущая.Кандидат = Актуальные.Кандидат
ГДЕ
    (Актуальные.ТекущееСостояние = &СтатусФильтр ИЛИ &СтатусФильтр = ЗНАЧЕНИЕ(Перечисление.СостоянияКандидатов.ПустаяСсылка))
;

Важный момент: использование функции РАЗНОСТЬДАТ с параметром СЕКУНДА позволяет получить максимально точный интервал. Если вам нужно вывести результат в формате "Дни:Часы:Минуты", рекомендуется выполнять деление остатка от секунд в итоговой выборке, а не полагаться на функции ДЕНЬ() или МЕСЯЦ() от пустой даты, так как они могут давать погрешность из-за особенностей календаря.

Оптимизация для больших объемов данных

Метод с использованием КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...) является ресурсоемким (сложность $O(n^2)$). Если в вашем регистре десятки тысяч записей по одному кандидату, запрос может работать медленно. В этом случае рассмотрим альтернативу: вместо нумерации строк можно соединять записи по условию "Максимальный период, который меньше текущего".

Посмотрим на пример такого соединения:


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

Этот способ часто работает быстрее на платформе 8.2, так как он более эффективно использует индексы временных таблиц. Не забудьте проиндексировать временные таблицы по полям Кандидат и Период для ускорения работы соединений.

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

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

Такой подход позволяет избежать ситуации, когда "Бирюкова Анна" попадает в отчет по фильтру "На рассмотрении", хотя ее текущий статус уже изменился. Мы обеспечили точность данных и корректность отображения времени обработки заявок.

← На главную