При автоматизации учета работы промышленного оборудования (например, скважин, станков, агрегатов) часто возникает задача: в периодическом регистре сведений фиксируются изменения состояний (например, «Работа», «Ремонт», «Простой»), и нам требуется за произвольный интервал времени рассчитать общую длительность нахождения каждого объекта в каждом из состояний.
Сложность заключается в том, что состояния могут меняться несколько раз в день или, наоборот, не меняться месяцами. Кроме того, на начало анализируемого периода состояние уже было установлено какой-то более ранней датой, а на конец периода последнее состояние действует вплоть до границы отчета. Разберем по шагам, почему типовые попытки сгруппировать даты не работают и как правильно реализовать этот расчет.
Проанализируем типичную ошибку начинающих разработчиков: попытку сгруппировать записи регистра по состоянию и взять разницу между МИНИМУМ(Период) и МАКСИМУМ(Период). Этот подход принципиально неверен, если состояние объекта менялось циклически:
Посмотрим на наиболее универсальный способ решения задачи на чистом языке запросов 1С. Поскольку в языке запросов платформы нет стандартных SQL-функций смещения (таких как LEAD или LAG), переход к следующей дате строится через внутреннее соединение таблицы самой с собой по неравенству дат и поиск минимальной даты среди более поздних.
Разберем логику работы алгоритма по этапам:
&Дата1 и виртуально устанавливаем ему дату &Дата1.&Дата1 до &Дата2.&Дата2 (срез последних на дату окончания), чтобы закрыть последний интервал.Раньше.Период < Позже.Период. Через агрегатную функцию МИНИМУМ(Позже.Период) определяем дату закрытия каждого интервала.РАЗНОСТЬДАТ (в секундах, минутах или днях) и суммируем результат с группировкой по объекту и состоянию.Рассмотрим готовый текст пакетного запроса:
ВЫБРАТЬ
&Дата1 КАК Период,
НаНачало.Скважина КАК Скважина,
НаНачало.Состояние КАК Состояние
ПОМЕСТИТЬ Дано
ИЗ
РегистрСведений.Резерв.СрезПоследних(&Дата1, ) КАК НаНачало
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Резерв.Период,
Резерв.Скважина,
Резерв.Состояние
ИЗ
РегистрСведений.Резерв КАК Резерв
ГДЕ
Резерв.Период МЕЖДУ &Дата1 И &Дата2
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
&Дата2,
НаКонец.Скважина,
НаКонец.Состояние
ИЗ
РегистрСведений.Резерв.СрезПоследних(&Дата2, ) КАК НаКонец
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Раньше.Период КАК Период,
Раньше.Скважина КАК Скважина,
Раньше.Состояние КАК Состояние,
РАЗНОСТЬДАТ(Раньше.Период, МИНИМУМ(Позже.Период), СЕКУНДА) КАК Длительность
ПОМЕСТИТЬ Интервалы
ИЗ
Дано КАК Раньше
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Дано КАК Позже
ПО Раньше.Скважина = Позже.Скважина
И Раньше.Период < Позже.Период
СГРУППИРОВАТЬ ПО
Раньше.Период,
Раньше.Скважина,
Раньше.Состояние
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Интервалы.Скважина КАК Скважина,
Интервалы.Состояние КАК Состояние,
СУММА(Интервалы.Длительность) КАК Длительность
ИЗ
Интервалы КАК Интервалы
СГРУППИРОВАТЬ ПО
Интервалы.Скважина,
Интервалы.Состояние
Обратим внимание на важную деталь: условие Раньше.Период < Позже.Период вместе с МИНИМУМ(Позже.Период) находит строго следующую точку изменения статуса, отсекая все более поздние дубли. Это позволяет корректно обработать даже ситуации, когда статус менялся несколько раз в течение одной минуты.
Проанализируем технические нюансы представленного выше решения:
Дано обязательно следует добавить директиву:
ИНДЕКСИРОВАТЬ ПО
Скважина,
Период
&Дата2. Если передать 30.11.2016 00:00:00, то события, произошедшие в течение этого дня, не попадут в интервал. Конец периода обычно задается как КонецДня(&Дата2).Если записей слишком много и пакетный запрос выполняется медленно, рассмотрим два альтернативных варианта реализации расчета:
1. Использование механизма выражений СКД
В Системе компоновки данных можно избежать квадратичного соединения таблиц на уровне СУБД. Запрос выбирает плоский список записей, отсортированный по дате, а следующая дата вычисляется на уровне платформы с помощью системной функции:
ВычислитьВыражение("Период", , , "Следующая", "Следующая")
В этом случае расчет выполняется платформой 1С в памяти за один линейный проход.
2. Линейный обход выборки на встроенном языке
Если отчет строится без СКД, оптимальным по скорости является простой выбор данных с сортировкой по Скважина, Период ВОЗР. Далее в коде модуля мы выполняем один последовательный проход:
ТекущаяСкважина = Неопределено;
ТекущееСостояние = Неопределено;
ВремяНачала = Неопределено;
Пока Выборка.Следующий() Цикл
Если Выборка.Скважина <> ТекущаяСкважина Тогда
ТекущаяСкважина = Выборка.Скважина;
ТекущееСостояние = Выборка.Состояние;
ВремяНачала = Выборка.Период;
Продолжить;
КонецЕсли;
Длительность = Выборка.Период - ВремяНачала;
// Накапливаем результат в структуру или ТаблицуЗначений
ВремяНачала = Выборка.Период;
ТекущееСостояние = Выборка.Состояние;
КонецЦикла;
3. Архитектурный подход с оборотным регистром накопления
Если задача расчета наработки оборудования является постоянной и критичной к скорости формирования отчетов, правильнее рассчитывать длительность интервала в момент проведения документов перевода состояний и записывать готовые секунды в оборотный регистр накопления. В таком случае отчет строится мгновенным обращением к виртуальной таблице РегистрНакопления.НаработкаОборудования.Обороты.