Как в запросе 1С или СКД рассчитать количество дней между документами-регистраторами?

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

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

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

Анализ типичных ошибок при самосоединении таблиц

Выясним причину, почему прямой запрос с условием Начала.Дата < Концы.Дата выдает некорректные сроки или дублирует строки:

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

Способ 1. Получение интервала между регистраторами через временные таблицы в запросе

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

Шаг 1. Во временную таблицу собираются пары: текущий регистратор (с датой) и ближайшая следующая дата движения по тому же договору с помощью агрегатной функции МИНИМУМ.

Шаг 2. Вторым пакетом к полученной таблице снова присоединяются данные регистратора (если требуется ссылка на закрывающий документ) и вычисляется разность дат через РАЗНОСТЬДАТ.

Посмотрим на пример текста запроса:


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

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

Способ 2. Расчет разницы дат средствами Системы компоновки данных (СКД)

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

Разберем настройку по шагам:

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

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

Преимущество этого метода заключается в высокой скорости выполнения (сложность $O(N)$ вместо $O(N^2)$ при самосоединении больших таблиц) и автоматическом сбросе расчета при смене группировки договора.

Способ 3. Получение срезов по периодичности «День»

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

В таких случаях оптимальным архитектурным решением является запрос к виртуальной таблице ОстаткиИОбороты с периодичностью ДЕНЬ (или разворачивание по таблице производственного календаря). При таком подходе остаток займа известен на каждый день, а процент рассчитывается по формуле посуточного начисления без необходимости поиска соседних регистраторов.

← На главную