В практике разработки на платформе 1С очень часто возникают ситуации, когда имеется плоская таблица с датами (или числами), и нам необходимо преобразовать этот набор точек во времени в полноценные интервалы (периоды "От" и "До"). Проанализируем эту задачу подробнее. Построение интервалов, на которые исходные даты разбивают временную ось, требуется в самых разнообразных подсистемах — от расчета зарплаты до сложной складской логистики.
Давайте вместе разберем по шагам, в каких именно задачах применяется такой механизм, посмотрим на пример классического запроса и выясним причину, по которой стандартный подход может приводить к серьезным проблемам с производительностью. Также мы рассмотрим современные и альтернативные архитектурные пути решения этой проблемы.
Посмотрим на пример типичной реализации. Допустим, у нас есть таблица, содержащая множество дат документов, установки цен или статусов. Чтобы получить интервал постоянства цены (от даты начала действия текущей цены до даты начала действия следующей), мы обычно используем самосоединение таблицы. Рассмотрим классический запрос:
ВЫБРАТЬ
ИсходнаяТаблицаСДатами.Период КАК Период
ПОМЕСТИТЬ ВТ_Даты
ИЗ
&ИсходнаяТаблицаСДатами КАК ИсходнаяТаблицаСДатами;
ВЫБРАТЬ
ВТ_Даты1.Период Как НачалоПериода,
МИНИМУМ(ВТ_Даты2.Период) КАК КонецПериода
ИЗ
ВТ_Даты КАК ВТ_Даты1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Даты КАК ВТ_Даты2
ПО ВТ_Даты2.Период > ВТ_Даты1.Период
СГРУППИРОВАТЬ ПО
ВТ_Даты1.Период;
Конструкция ЛЕВОЕ СОЕДИНЕНИЕ по условию > (или <) позволяет найти для каждой даты ближайшую следующую за ней точку на временной оси. Однако, при больших объемах данных такое соединение таблицы самой с собой начинает работать недопустимо долго из-за квадратичной сложности алгоритма (серверу СУБД приходится перебирать огромное количество комбинаций). Тем не менее, потребность в таких данных огромна.
Рассмотрим подробнее реальные сценарии, с которыми разработчики сталкиваются в различных отраслях. Проанализируем ситуации, где без вычисления интервалов обойтись практически невозможно:
СрезПоследних у периодических регистров сведений позволяет получить данные только на конкретную дату (параметр). Если руководителю требуется отчет по фактической численности сотрудников или изменению цен за каждый месяц в динамике, вызывать срез в цикле — плохая практика. Запрос с определением интервалов позволяет получить динамический срез за один проход базы.Выясним причину, почему простым запросом не всегда удается обойтись. Разберем более сложные вариации задач с интервалами:
Проанализируем ситуацию: в крупных компаниях (например, штат более 1000 сотрудников или миллионы чеков) расчет интервалов динамическим запросом с ЛЕВОЕ СОЕДИНЕНИЕ "вешает" сервер СУБД. Выполнение может занимать минуты. Как с этим бороться? Рассмотрим несколько надежных подходов:
Вместо того чтобы вычислять интервалы "на лету" в момент формирования отчета, мы можем сохранять их заранее. Добавляется новый периодический регистр сведений (или накопления), который хранит уже готовые показатели в нужной периодичности (например, по дням). Заполнение этого регистра производится специальным регламентным заданием в фоновом режиме по ночам. В случае необходимости перерасчета из-за изменения документов "задним числом" регистр точечно перезаполняется. При таком подходе время формирования тяжелых отчетов сокращается с минут до долей секунды.
Если речь идет о ценах или скидках, в исходный регистр часто добавляют дополнительный реквизит ДатаОкончания. Когда вводится новый документ установки цен, он при проведении автоматически находит предыдущую запись по этому товару и записывает в ее реквизит ДатаОкончания дату, предшествующую новой установке. Это позволяет в отчетах использовать стандартное условие МЕЖДУ, что работает мгновенно благодаря индексам.
Для задач типа СКУД (вход-выход), где нужно анализировать много краевых логических условий, использовать сложный запрос нерационально. Зачастую гораздо эффективнее выбрать все события линейным запросом, отсортировать их по времени и обработать за один проход по выборке в коде модуле 1С. Алгоритмы сложности O(N) в оперативной памяти сервера 1С отработают быстрее, чем тяжеловесные соединения таблиц на стороне СУБД.
Пример со складом: нужно контролировать время между наборами товаров сотрудниками. Вместо построения огромного запроса по всем сборкам, мы внедряем логику прямо в процесс. При начале работы над новой накладной код 1С находит предыдущий документ сотрудника, рассчитывает разницу во времени и сохраняет это значение (количество секунд или минут) прямо в реквизит текущего документа. Итог: сложный анализ заменяется банальной выборкой готовых чисел из реквизитов.
Рассмотрим подробнее новые возможности, которые разработчики получили в последних версиях платформы 1С:Предприятие.
Оконные функции: Начиная с версии платформы 8.3.20, в язык запросов 1С были добавлены оконные функции. Это кардинально меняет правила игры. Теперь для получения следующего или предыдущего значения (начала нового интервала) нам больше не нужно соединять таблицу саму с собой по условию "больше-меньше". Мы можем использовать оконные функции смещения (аналоги LEAD и LAG в SQL). Получение даты следующего события в рамках группировки (например, по конкретному товару) выполняется линейно, за один проход базы данных сервером СУБД. Это решает проблему производительности на корню.
Математические линейные алгоритмы: Для старых платформ (или сложных пересечений множеств) применяются концепции вычисления "нарастающих итогов" и метод "Элементарных интервалов". В этом случае все точки (даты начала и конца любых событий) сваливаются во временную таблицу без дублей, сортируются, а затем каждому событию присваивается "плюс 1" или "минус 1". Складывая эти значения по времени, мы получаем число, показывающее количество активных интервалов в любой момент времени.
Подводя итог, можно с уверенностью сказать, что задачи работы с интервалами встречаются повсеместно. Понимание того, как правильно вычислять длительности, схлопывать периоды и избегать декартовых соединений в запросах — обязательный навык для любого профессионального разработчика 1С, стремящегося создавать быстрые и отказоустойчивые системы.