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

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

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача преобразования набора дискретных записей (например, поминутной занятости оборудования или сотрудников) в наглядные временные интервалы. Типичный пример: в регистре сведений хранятся записи по минутам с признаком Занято (Булево). Нам же требуется получить результат в виде: «с 1-й по 10-ю минуту — Свободно, с 11-й по 25-ю — Занято». В SQL эта задача известна как Gaps and Islands («Разрывы и острова»).

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

Постановка задачи и анализ структуры данных

Предположим, у нас имеется непериодический регистр сведений ПланированиеВремени со следующей структурой:

Проблема прямого выбора заключается в том, что обычная группировка по значению Занято схлопнет все минуты дня в две строки, потеряв хронологическую последовательность. Нам же нужно сохранить порядок и выделить каждый непрерывный блок отдельно. Рассмотрим пошагово, как это сделать.

Шаг 1. Подготовка данных во временной таблице

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


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

Шаг 2. Поиск точек изменения состояния (границ интервалов)

Основная идея решения заключается в определении моментов, когда значение ресурса Занято меняется по сравнению с предыдущей записью. Проанализируем ситуацию: если для текущей минуты значение Занято отличается от значения в предыдущей минуте, значит, начался новый интервал.

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

Шаг 3. Группировка по «островам» с использованием вложенных запросов

Для каждой минуты нам нужно найти ближайшую предшествующую минуту, в которой состояние было иным. Все записи, имеющие одну и ту же «ближайшую границу изменения», относятся к одному интервалу.

Посмотрим на пример логики формирования временной таблицы ВТ_Границы:


ВЫБРАТЬ
    М1.НомерМинуты КАК НомерМинуты,
    М1.Занято КАК Занято,
    МАКСИМУМ(М11.НомерМинуты) КАК НомерМинутыПредыдущегоИзменения
ПОМЕСТИТЬ ВТ_РазмеченныеДанные
ИЗ
    ВТ_ИсходныеДанные КАК М1
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИсходныеДанные КАК М11
        ПО М1.НомерМинуты > М11.НомерМинуты
            И М1.Занято <> М11.Занято
ГРУППИРОВАТЬ ПО
    М1.НомерМинуты,
    М1.Занято;

В этой таблице поле НомерМинутыПредыдущегоИзменения будет содержать NULL для самого первого интервала в дне и номер последней минуты предыдущего интервала для всех последующих. Это идеальный маркер для группировки.

Шаг 4. Формирование итоговых интервалов

Теперь нам остается сгруппировать данные по найденному маркеру границы и значению ресурса. Минимальный номер минуты в группе станет началом периода, а максимальный — концом.


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

Важный нюанс: Обратите внимание на конструкцию ЕСТЬNULL(ВТ.НомерМинутыПредыдущегоИзменения + 1, 1). Она корректно обрабатывает самый первый интервал дня, устанавливая его начало в 1-ю минуту, а для всех последующих интервалов прибавляет единицу к моменту последнего изменения статуса.

Анализ производительности и альтернативные методы

Рассмотрим ситуацию, когда данных очень много (например, тысячи рабочих мест). Соединение таблицы ВТ_ИсходныеДанные самой с собой по условию М1.НомерМинуты > М11.НомерМинуты имеет квадратичную сложность O(n²). Если в сутках 1440 минут, то для одного рабочего места запрос отработает быстро, но при массовом расчете за месяц производительность может упасть.

В таких случаях рекомендуется:

  1. Использовать нумерацию строк. Хотя в языке запросов 1С нет функции ROW_NUMBER(), её можно имитировать. Разность между общим порядковым номером записи и порядковым номером внутри группы (где Занято = Истина) будет постоянной для одного непрерывного интервала.
  2. Обработка выборки в коде. Если запрос становится слишком сложным или медленным, иногда эффективнее выбрать «плоский» список минут, отсортированный по времени, и обойти его в одном цикле на языке 1С.

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


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

Пока Выборка.Следующий() Цикл
    Если Выборка.Занято <> ТекущееСостояние Тогда
        Если ТекущееСостояние <> Неопределено Тогда
            // Фиксируем законченный интервал
            НоваяСтрока = ТаблицаРезультат.Добавить();
            НоваяСтрока.Начало = НачалоИнтервала;
            НоваяСтрока.Конец = Выборка.НомерМинуты - 1;
            НоваяСтрока.Статус = ТекущееСостояние;
        КонецЕсли;
        
        НачалоИнтервала = Выборка.НомерМинуты;
        ТекущееСостояние = Выборка.Занято;
    КонецЕсли;
КонецЦикла;

Заключение

Мы разобрали классический способ решения задачи группировки последовательностей в 1С. Использование временных таблиц и корреляционных соединений позволяет получить результат в виде компактного списка интервалов прямо в запросе. Это удобно для последующего вывода в отчеты (СКД) или использования в других запросах. Однако всегда помните о нагрузке на сервер БД: для очень больших массивов данных алгоритм «поиска предыдущего» через соединение может потребовать оптимизации или переноса логики на сторону встроенного языка.

← На главную