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

Программист 1С v8.3 (Управляемые формы) Управленческий учет Промышленность, строительство и АПК
← На главную

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

Сложность заключается в том, что состояния могут меняться несколько раз в день или, наоборот, не меняться месяцами. Кроме того, на начало анализируемого периода состояние уже было установлено какой-то более ранней датой, а на конец периода последнее состояние действует вплоть до границы отчета. Разберем по шагам, почему типовые попытки сгруппировать даты не работают и как правильно реализовать этот расчет.

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

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

  1. Если скважина работала со 2 по 5 число, затем ушла в ремонт с 5 по 10 число, а с 10 по 15 число снова вернулась в работу, то разность дат между 2 и 15 числом включит в себя и дни ремонта, задвоив итоговые показатели.
  2. Любое состояние действует непрерывно от момента его установки до момента фиксации следующего состояния (или до конца отчета). Без связывания каждой точки со следующей точкой корректный расчет невозможен.

Классическое решение: пакетный запрос с вычислением смежных интервалов

Посмотрим на наиболее универсальный способ решения задачи на чистом языке запросов 1С. Поскольку в языке запросов платформы нет стандартных SQL-функций смещения (таких как LEAD или LAG), переход к следующей дате строится через внутреннее соединение таблицы самой с собой по неравенству дат и поиск минимальной даты среди более поздних.

Разберем логику работы алгоритма по этапам:

  1. Получаем состояние объекта на начало интервала с помощью среза последних на &Дата1 и виртуально устанавливаем ему дату &Дата1.
  2. Выбираем все изменения состояний внутри периода от &Дата1 до &Дата2.
  3. Добавляем фиктивную замыкающую точку на &Дата2 (срез последних на дату окончания), чтобы закрыть последний интервал.
  4. Соединяем полученный набор данных сам с собой по условию Раньше.Период < Позже.Период. Через агрегатную функцию МИНИМУМ(Позже.Период) определяем дату закрытия каждого интервала.
  5. Рассчитываем длительность интервала функцией РАЗНОСТЬДАТ (в секундах, минутах или днях) и суммируем результат с группировкой по объекту и состоянию.

Рассмотрим готовый текст пакетного запроса:


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

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

ВЫБРАТЬ
    Резерв.Период,
    Резерв.Скважина,
    Резерв.Состояние
ИЗ
    РегистрСведений.Резерв КАК Резерв
ГДЕ
    Резерв.Период МЕЖДУ &Дата1 И &Дата2

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

ВЫБРАТЬ
    &Дата2,
    НаКонец.Скважина,
    НаКонец.Состояние
ИЗ
    РегистрСведений.Резерв.СрезПоследних(&Дата2, ) КАК НаКонец
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Раньше.Период КАК Период,
    Раньше.Скважина КАК Скважина,
    Раньше.Состояние КАК Состояние,
    РАЗНОСТЬДАТ(Раньше.Период, МИНИМУМ(Позже.Период), СЕКУНДА) КАК Длительность
ПОМЕСТИТЬ Интервалы
ИЗ
    Дано КАК Раньше
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Дано КАК Позже
        ПО Раньше.Скважина = Позже.Скважина
        И Раньше.Период < Позже.Период
СГРУППИРОВАТЬ ПО
    Раньше.Период,
    Раньше.Скважина,
    Раньше.Состояние
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Интервалы.Скважина КАК Скважина,
    Интервалы.Состояние КАК Состояние,
    СУММА(Интервалы.Длительность) КАК Длительность
ИЗ
    Интервалы КАК Интервалы
СГРУППИРОВАТЬ ПО
    Интервалы.Скважина,
    Интервалы.Состояние

Обратим внимание на важную деталь: условие Раньше.Период < Позже.Период вместе с МИНИМУМ(Позже.Период) находит строго следующую точку изменения статуса, отсекая все более поздние дубли. Это позволяет корректно обработать даже ситуации, когда статус менялся несколько раз в течение одной минуты.

Узкие места и вопросы производительности соединения по неравенству

Проанализируем технические нюансы представленного выше решения:

  1. Квадратичная сложность: Соединение таблицы самой с собой по условию «меньше» приводит к лавинообразному перемножению строк до того, как сработает группировка. Если записей по одной скважине тысячи, СУБД будет тратить значительные ресурсы на построение декартова произведения во временных таблицах.
  2. Индексация временных таблиц: Чтобы оптимизатор СУБД не выполнял полное сканирование (Table Scan), во временной таблице Дано обязательно следует добавить директиву:
    
    ИНДЕКСИРОВАТЬ ПО
        Скважина,
        Период
    
  3. Повторяющиеся статусы: Если в регистр периодически записываются подтверждающие записи без смены фактического статуса (например, «Работа» и через час снова «Работа»), интервалы будут раздроблены, но итоговая сумма секунд останется корректной. Тем не менее, лишние точки увеличивают нагрузку на запрос.
  4. Границы периодов: При точности до секунды необходимо следить за границей &Дата2. Если передать 30.11.2016 00:00:00, то события, произошедшие в течение этого дня, не попадут в интервал. Конец периода обычно задается как КонецДня(&Дата2).

Альтернативные способы реализации

Если записей слишком много и пакетный запрос выполняется медленно, рассмотрим два альтернативных варианта реализации расчета:

1. Использование механизма выражений СКД
В Системе компоновки данных можно избежать квадратичного соединения таблиц на уровне СУБД. Запрос выбирает плоский список записей, отсортированный по дате, а следующая дата вычисляется на уровне платформы с помощью системной функции:

ВычислитьВыражение("Период", , , "Следующая", "Следующая")

В этом случае расчет выполняется платформой 1С в памяти за один линейный проход.

2. Линейный обход выборки на встроенном языке
Если отчет строится без СКД, оптимальным по скорости является простой выбор данных с сортировкой по Скважина, Период ВОЗР. Далее в коде модуля мы выполняем один последовательный проход:


ТекущаяСкважина = Неопределено;
ТекущееСостояние = Неопределено;
ВремяНачала = Неопределено;

Пока Выборка.Следующий() Цикл
    Если Выборка.Скважина <> ТекущаяСкважина Тогда
        ТекущаяСкважина = Выборка.Скважина;
        ТекущееСостояние = Выборка.Состояние;
        ВремяНачала = Выборка.Период;
        Продолжить;
    КонецЕсли;

    Длительность = Выборка.Период - ВремяНачала;
    // Накапливаем результат в структуру или ТаблицуЗначений
    ВремяНачала = Выборка.Период;
    ТекущееСостояние = Выборка.Состояние;
КонецЦикла;

3. Архитектурный подход с оборотным регистром накопления
Если задача расчета наработки оборудования является постоянной и критичной к скорости формирования отчетов, правильнее рассчитывать длительность интервала в момент проведения документов перевода состояний и записывать готовые секунды в оборотный регистр накопления. В таком случае отчет строится мгновенным обращением к виртуальной таблице РегистрНакопления.НаработкаОборудования.Обороты.

← На главную