В типовых конфигурациях 1С большинство отчетов строится на основе календарных периодов: день, неделя, месяц или год. Однако в управленческом анализе часто возникает задача отслеживания активности клиентов (например, звонков, заказов или оплат) в рамках индивидуальных жизненных циклов. Это означает, что для Контрагента А "неделя" может начинаться в понедельник, а для Контрагента Б — в среду, в зависимости от даты их первого взаимодействия с компанией. Рассмотрим подробно, как реализовать такой механизм с помощью запросов и вспомогательных объектов платформы 1С.
Традиционный подход к учету предполагает использование функции НАЧАЛОПЕРИОДА. Если мы хотим посчитать звонки за неделю, мы просто ограничиваем выборку условием Дата МЕЖДУ &НачалоНедели И &КонецНедели. В этом случае все звонки всех клиентов попадают в одни и те же временные рамки.
Скользящий период (rolling period) работает иначе. Нам необходимо:
Такой подход позволяет проводить когортный анализ: мы можем сравнивать поведение клиентов на их первой, второй или третьей неделе обслуживания, независимо от того, в какой календарный день они к нам пришли.
Проанализируем наиболее эффективный способ реализации этой задачи через запрос. Мы разделим алгоритм на несколько этапов, используя временные таблицы для оптимизации производительности. Проанализируем ситуацию на примере учета звонков.
Для начала нам нужно найти точку отсчета для каждого клиента. Воспользуемся агрегатной функцией МИНИМУМ:
ВЫБРАТЬ
ТаблицаЗвонков.Контрагент КАК Контрагент,
МИНИМУМ(ТаблицаЗвонков.ДатаЗвонка) КАК ДатаПервогоЗвонка
ПОМЕСТИТЬ ВТ_ПервыеЗвонки
ИЗ
РегистрСведений.Звонки КАК ТаблицаЗвонков
СГРУППИРОВАТЬ ПО
ТаблицаЗвонков.Контрагент;
После того как мы получили таблицу ВТ_ПервыеЗвонки, мы можем построить сетку периодов. Рассмотрим пример, где мы хотим рассчитать количество звонков именно в первую неделю от момента касания. Разберем по шагам соединение основной таблицы звонков с нашей временной таблицей:
ВЫБРАТЬ
Звонки.Контрагент КАК Контрагент,
ВТ_ПервыеЗвонки.ДатаПервогоЗвонка КАК НачалоИндивидуальнойНедели,
ДОБАВИТЬКДАТЕ(ВТ_ПервыеЗвонки.ДатаПервогоЗвонка, ДЕНЬ, 6) КАК КонецИндивидуальнойНедели,
КОЛИЧЕСТВО(Звонки.Ссылка) КАК КоличествоЗвонков
ИЗ
ВТ_ПервыеЗвонки КАК ВТ_ПервыеЗвонки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.Звонки КАК Звонки
ПО ВТ_ПервыеЗвонки.Контрагент = Звонки.Контрагент
И Звонки.ДатаЗвонка МЕЖДУ ВТ_ПервыеЗвонки.ДатаПервогоЗвонка
И ДОБАВИТЬКДАТЕ(ВТ_ПервыеЗвонки.ДатаПервогоЗвонка, ДЕНЬ, 6)
СГРУППИРОВАТЬ ПО
Звонки.Контрагент,
ВТ_ПервыеЗвонки.ДатаПервогоЗвонка;
В данном примере мы жестко ограничили выборку только первой неделей. Если же нам нужно вывести все недели по порядку, подход усложняется.
Для вывода списка всех периодов (1-я неделя, 2-я неделя и т.д.) без создания бесконечного количества соединений, можно использовать расчет "номера периода" от базовой даты. Это позволит сгруппировать данные по вычисляемому полю.
Выясним причину, почему это удобнее: использование функции РАЗНОСТЬДАТ позволяет нам получить количество дней между первым звонком и текущим, а затем простым делением определить номер интервала. Рассмотрим пример кода:
ВЫБРАТЬ
Звонки.Контрагент КАК Контрагент,
ВТ_ПервыеЗвонки.ДатаПервогоЗвонка КАК БазоваяДата,
// Вычисляем номер недели (0 - первая неделя, 1 - вторая и т.д.)
ВЫРАЗИТЬ((РАЗНОСТЬДАТ(ВТ_ПервыеЗвонки.ДатаПервогоЗвонка, Звонки.ДатаЗвонка, ДЕНЬ)) / 7 КАК ЧИСЛО(10, 0)) КАК НомерНедели,
КОЛИЧЕСТВО(*) КАК КолВоЗвонков
ИЗ
РегистрСведений.Звонки КАК Звонки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ПервыеЗвонки КАК ВТ_ПервыеЗвонки
ПО Звонки.Контрагент = ВТ_ПервыеЗвонки.Контрагент
СГРУППИРОВАТЬ ПО
Звонки.Контрагент,
ВТ_ПервыеЗвонки.ДатаПервогоЗвонка,
(РАЗНОСТЬДАТ(ВТ_ПервыеЗвонки.ДатаПервогоЗвонка, Звонки.ДатаЗвонка, ДЕНЬ)) / 7;
Важно помнить, что в языке запросов 1С при делении целых чисел результат может округляться по разным правилам в зависимости от используемой СУБД. Поэтому иногда надежнее использовать конструкцию ВЫБОР КОГДА или предварительно рассчитанную таблицу интервалов.
Если бизнес-требования подразумевают, что "смещение" периода может меняться (например, для VIP-клиентов период месяц, а для обычных — неделя), целесообразно создать вспомогательный Регистр сведений СмещенияПериодов. В этом регистре можно хранить индивидуальные настройки для каждого сотрудника или контрагента.
Посмотрим на пример запроса, который учитывает индивидуальное смещение в днях, заданное в ресурсе регистра СмещениеПериодаДней:
ВЫБРАТЬ
ВТЗвонки.АвторЗвонка КАК АвторЗвонка,
КОЛИЧЕСТВО(ВЫБОР
КОГДА ДОБАВИТЬКДАТЕ(ВТЗвонки.ДатаЗвонка, ДЕНЬ, -Настройки.СмещениеПериодаДней) < &НачалоПериода
ТОГДА NULL
ИНАЧЕ ВТЗвонки.Звонок
КОНЕЦ) КАК ЗвонокКолВо
ИЗ
ВТЗвонки КАК ВТЗвонки
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.НастройкиПериодов КАК Настройки
ПО Настройки.Объект = ВТЗвонки.АвторЗвонка
ГДЕ
ВТЗвонки.ДатаЗвонка МЕЖДУ &ДатаС И &ДатаПо
СГРУППИРОВАТЬ ПО
ВТЗвонки.АвторЗвонка;
Этот метод позволяет динамически "сдвигать" границы календарного периода для каждого пользователя, делая отчетность более гибкой.
При работе с большими объемами данных (сотни тысяч звонков) расчет "на лету" через МИНИМУМ и соединения временных таблиц может существенно замедлить работу системы. Проанализируем, как можно оптимизировать этот процесс:
Контрагент и ДатаЗвонка в регистре звонков обязательно должны быть проиндексированы. В языке запросов при создании временных таблиц всегда используйте конструкцию ИНДЕКСИРОВАТЬ ПО.РегистрНакопления. При проведении документа "Звонок" система может программно определять дату первого звонка клиента (хранящуюся в отдельном регистре сведений) и записывать в измерение регистра накопления уже готовый период (например, дату начала индивидуальной недели).При использовании Системы компоновки данных (СКД) для таких отчетов, рекомендуется:
Таким образом, реализация индивидуальных периодов в 1С требует отхода от стандартных функций работы с датами и перехода к логике вычисления смещений. Наиболее стабильным и производительным решением является сочетание временных таблиц с предварительным поиском "даты инициации" клиента.