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

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

При разработке отчетов в 1С программисты часто сталкиваются с необходимостью вычислить время, прошедшее между двумя событиями, которые зафиксированы в регистре сведений. Например, сколько времени прошло с момента создания заказа до его выполнения. Сложность заключается в том, что каждое событие — это отдельная строка в регистре, а стандартная функция РАЗНОСТЬДАТ предназначена для работы со значениями в рамках одной строки. Разберем подробно, как решить эту задачу с помощью языка запросов.

Анализ проблемы: почему РАЗНОСТЬДАТ выдает пустой результат?

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


РАЗНОСТЬДАТ(
    ВЫБОР КОГДА СтатусЗаказа = ЗНАЧЕНИЕ(Перечисление.Статусы.Выполнен) ТОГДА Период КОНЕЦ,
    ВЫБОР КОГДА СтатусЗаказа = ЗНАЧЕНИЕ(Перечисление.Статусы.Создан) ТОГДА Период КОНЕЦ,
    СЕКУНДА
)

Проанализируем ситуацию: для системы 1С каждая запись в регистре сведений уникальна. В одной конкретной строке статус не может быть одновременно и «Создан», и «Выполнен». Следовательно, в одной строке первое выражение ВЫБОР вернет дату, а второе — NULL (или пустую дату), и наоборот. Функция РАЗНОСТЬДАТ не может корректно обработать NULL, поэтому результатом становится пустое значение или ошибка.

Решение задачи через группировку и агрегатные функции

Чтобы вычислить разницу, нам нужно «схлопнуть» две разные строки (событие А и событие Б) в одну. Для этого мы используем механизм группировки по ключевому полю (например, по Заказу) и агрегатную функцию МАКСИМУМ.

Разберем по шагам создание правильного запроса:

  1. Выделение дат в отдельные колонки: Для каждого нужного нам статуса мы создаем свое поле, используя конструкцию ВЫБОР.
  2. Группировка: Схлопываем записи по объекту, чтобы в итоговой строке для одного заказа заполнились обе колонки с датами.
  3. Вычисление разности: На основе полученной таблицы считаем итоговый результат.

Пример реализации через временную таблицу:


// Шаг 1: Получаем таблицу, где даты начала и конца стоят в одной строке
ВЫБРАТЬ
    ИсторияСтатусов.Заказ КАК Заказ,
    МАКСИМУМ(ВЫБОР
            КОГДА ИсторияСтатусов.СтатусЗаказа = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Создан)
                ТОГДА ИсторияСтатусов.Период
            ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
        КОНЕЦ) КАК ДатаСоздания,
    МАКСИМУМ(ВЫБОР
            КОГДА ИсторияСтатусов.СтатусЗаказа = ЗНАЧЕНИЕ(Перечисление.СтатусыЗаказов.Выполнен)
                ТОГДА ИсторияСтатусов.Период
            ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
        КОНЕЦ) КАК ДатаВыполнения
ПОМЕСТИТЬ ВТ_ДатыЗаказов
ИЗ
    РегистрСведений.ИсторияСтатусовЗаказов КАК ИсторияСтатусов
СГРУППИРОВАТЬ ПО
    ИсторияСтатусов.Заказ;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Вычисляем разницу между полученными датами
ВЫБРАТЬ
    ВТ_ДатыЗаказов.Заказ,
    ВТ_ДатыЗаказов.ДатаСоздания,
    ВТ_ДатыЗаказов.ДатаВыполнения,
    РАЗНОСТЬДАТ(ВТ_ДатыЗаказов.ДатаСоздания, ВТ_ДатыЗаказов.ДатаВыполнения, СЕКУНДА) КАК ДлительностьВСекундах
ИЗ
    ВТ_ДатыЗаказов КАК ВТ_ДатыЗаказов
ГДЕ
    ВТ_ДатыЗаказов.ДатаСоздания > ДАТАВРЕМЯ(1, 1, 1)
    И ВТ_ДатыЗаказов.ДатаВыполнения > ДАТАВРЕМЯ(1, 1, 1)

Важный нюанс: Обратите внимание на использование ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1). Если этого не сделать, функция МАКСИМУМ может вернуть NULL, что помешает дальнейшим расчетам. В финальной выборке мы добавили условие ГДЕ, чтобы отсечь заказы, которые еще не прошли оба статуса.

Оптимизация вывода: перевод секунд в читаемый формат

Функция РАЗНОСТЬДАТ возвращает целое число. Если мы получили разницу в секундах (например, 3665 секунд), пользователю будет неудобно это читать. Нам нужно привести это к виду «1 час 1 минута 5 секунд».

Рассмотрим, как вычислить компоненты времени прямо в запросе (хотя это делает его громоздким):

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

ЦелоеЧисло(РазностьВСекундах / 3600) (для получения часов в СКД).

Использование оконных функций (для платформы 8.3.20 и выше)

Если ваша конфигурация работает на современной платформе, задачу «получить время следующего события» можно решить гораздо проще. Рассмотрим использование функции ВЫЧИСЛИТЬ_ВЫРАЖЕНИЕ.

В языке запросов для СКД можно использовать оператор СЛЕДУЮЩИЙ, чтобы получить период следующей записи для того же заказа, не прибегая к сложным группировкам. Это позволяет рассчитать время пребывания в каждом статусе, а не только между двумя конкретными.

Профессиональные советы по работе с разностью дат

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

  1. Учет рабочих графиков: Обычная разность дат учитывает и ночь, и выходные дни. Если задача стоит — вычислить именно рабочее время, необходимо соединять таблицу статусов с регистром сведений ГрафикиРаботы.
  2. Обработка пустых значений: Всегда проверяйте даты на заполненность перед вычислением разности. В SQL-подобных запросах разница между корректной датой и пустой может привести к непредсказуемым результатам (очень большим числам).
  3. Производительность: Если регистр ИсторияСтатусов содержит миллионы записей, группировка МАКСИМУМ(ВЫБОР...) может работать медленно. В этом случае рассмотрите возможность записи «Длительности нахождения в статусе» прямо в регистр при смене статуса программно. Тогда отчет превратится в простое суммирование ресурса.

Подведем итог: для вычисления разницы между периодами разных строк регистра сведений мы используем группировку по объекту и агрегацию дат через МАКСИМУМ. Это позволяет собрать нужные данные в одну строку и применить к ним стандартную функцию РАЗНОСТЬДАТ.

← На главную