При решении учетных задач (например, расчет процентов по выданным или полученным займам, вычисление сроков оборачиваемости, анализ задержек поставок) возникает необходимость определить разницу в днях между двумя последовательными движениями по договору или контрагенту.
В языке запросов 1С отсутствуют классические оконные функции смещения (такие как LEAD или LAG), поэтому простая попытка связать таблицу саму с собой по условию неравенства дат часто приводит к декартову произведению строк, ошибкам в группировках и искажению выборки. Разберем основные причины таких ошибок и рассмотрим надежные варианты решения данной задачи.
Выясним причину, почему прямой запрос с условием Начала.Дата < Концы.Дата выдает некорректные сроки или дублирует строки:
МИНИМУМ() по всей совокупности следующих дат, а группирует данные по каждой паре дат отдельно.ДоговорКонтрагента или Контрагент), иначе произойдет перекрестное соединение документов разных договоров.ВНУТРЕННЕЕ СОЕДИНЕНИЕ эта строка просто исчезнет из итоговой таблицы. Чтобы сохранить последнюю запись, требуется использовать ЛЕВОЕ СОЕДИНЕНИЕ и функцию ЕСТЬNULL.Рассмотрим по шагам классический и универсальный алгоритм на языке запросов 1С. Логика строится на поиске минимальной даты, которая строго больше даты текущего документа.
Шаг 1. Во временную таблицу собираются пары: текущий регистратор (с датой) и ближайшая следующая дата движения по тому же договору с помощью агрегатной функции МИНИМУМ.
Шаг 2. Вторым пакетом к полученной таблице снова присоединяются данные регистратора (если требуется ссылка на закрывающий документ) и вычисляется разность дат через РАЗНОСТЬДАТ.
Посмотрим на пример текста запроса:
ВЫБРАТЬ
ДвиженияНачало.Период КАК ПериодНачала,
ДвиженияНачало.Регистратор КАК РегистраторНачала,
ДвиженияНачало.ДоговорКонтрагента КАК ДоговорКонтрагента,
МИНИМУМ(ДвиженияКонец.Период) КАК ПериодКонец
ПОМЕСТИТЬ ВТ_Интервалы
ИЗ
РегистрНакопления.ВзаиморасчетыСКонтрагентами.Обороты(, , Регистратор, ) КАК ДвиженияНачало
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Обороты(, , Регистратор, ) КАК ДвиженияКонец
ПО ДвиженияНачало.ДоговорКонтрагента = ДвиженияКонец.ДоговорКонтрагента
И ДвиженияНачало.Период < ДвиженияКонец.Период
СГРУППИРОВАТЬ ПО
ДвиженияНачало.ДоговорКонтрагента,
ДвиженияНачало.Период,
ДвиженияНачало.Регистратор
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Интервалы.ДоговорКонтрагента КАК ДоговорКонтрагента,
Интервалы.ПериодНачала КАК ПериодНачала,
Интервалы.РегистраторНачала КАК РегистраторНачала,
Интервалы.ПериодКонец КАК ПериодКонец,
ДвиженияКонец.Регистратор КАК РегистраторКонец,
РАЗНОСТЬДАТ(Интервалы.ПериодНачала, ЕСТЬNULL(Интервалы.ПериодКонец, Интервалы.ПериодНачала), ДЕНЬ) КАК КоличествоДней
ИЗ
ВТ_Интервалы КАК Интервалы
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ВзаиморасчетыСКонтрагентами.Обороты(, , Регистратор, ) КАК ДвиженияКонец
ПО Интервалы.ДоговорКонтрагента = ДвиженияКонец.ДоговорКонтрагента
И Интервалы.ПериодКонец = ДвиженияКонец.Период
УПОРЯДОЧИТЬ ПО
ДоговорКонтрагента,
ПериодНачала
Если отчет разрабатывается в СКД, значительно эффективнее отказаться от сложных самосоединений в тексте запроса и перенести расчет смещения в вычисляемые поля. В СКД для этого предназначена специализированная функция ВычислитьВыражение.
Разберем настройку по шагам:
Период, Регистратор, ДоговорКонтрагента, суммы движений и остатков.КоличествоДней.
РазностьДат(Период, ВычислитьВыражение("Период", , , "Следующая", "Следующая"), "День")
Преимущество этого метода заключается в высокой скорости выполнения (сложность $O(N)$ вместо $O(N^2)$ при самосоединении больших таблиц) и автоматическом сбросе расчета при смене группировки договора.
Для начисления процентов по займам расчет разницы между документами не всегда удобен, если процентная ставка меняется со временем или внутри длительного периода без движений наступают границы месяцев.
В таких случаях оптимальным архитектурным решением является запрос к виртуальной таблице ОстаткиИОбороты с периодичностью ДЕНЬ (или разворачивание по таблице производственного календаря). При таком подходе остаток займа известен на каждый день, а процент рассчитывается по формуле посуточного начисления без необходимости поиска соседних регистраторов.