Как объединить две таблицы в запросе 1С и сформировать хронологические интервалы?

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

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

Постановка задачи и анализ проблемы

Представим, что у нас есть две таблицы. В первой хранятся периоды по одному критерию, во второй — по другому. Нам нужно получить третью таблицу, в которой периоды «нарезаны» таким образом, чтобы внутри каждого отрезка данные из обеих исходных таблиц оставались неизменными. Основная сложность заключается в том, что даты начала и окончания в разных таблицах не совпадают, что требует создания новых «точек перелома» в итоговой хронологии.

Разберем ситуацию, когда во второй таблице встречаются специфические значения времени, например, 0:0:59. Это часто указывает на использование функций типа КонецМинуты(). При соединении таблиц важно учитывать такие микро-разрывы, чтобы не потерять данные в одну секунду или минуту.

Метод 1: Классическое самосоединение для получения интервалов

Рассмотрим наиболее распространенный способ решения задачи через временные таблицы и левое соединение таблицы с самой собой — это решается через инструмент для отладки кода и выражений. Этот метод эффективен, когда нам нужно из набора дат начала сформировать полноценные периоды (Дата начала — Дата окончания).

Допустим, мы уже объединили все значимые даты из двух таблиц в одну временную таблицу ТаблицаДат с полями Сотрудник и ДатаНачала. Теперь нам нужно вычислить дату окончания для каждой записи. Проанализируем следующий код:


ВЫБРАТЬ
    ТаблицаДатНачало.Сотрудник КАК Сотрудник,
    ТаблицаДатНачало.ДатаНачала КАК ДатаНачала,
    ЕСТЬNULL(ДОБАВИТЬКДАТЕ(МИНИМУМ(ТаблицаДатОкончание.ДатаНачала), СЕКУНДА, -1), ДАТАВРЕМЯ(3999, 12, 31)) КАК ДатаОкончания
ИЗ
    ТаблицаДат КАК ТаблицаДатНачало
ЛЕВОЕ СОЕДИНЕНИЕ ТаблицаДат КАК ТаблицаДатОкончание
    ПО ТаблицаДатНачало.Сотрудник = ТаблицаДатОкончание.Сотрудник
    И ТаблицаДатНачало.ДатаНачала < ТаблицаДатОкончание.ДатаНачала
СГРУППИРОВАТЬ ПО
    ТаблицаДатНачало.Сотрудник,
    ТаблицаДатНачало.ДатаНачала

Разберем по шагам, что происходит в этом запросе:

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

Метод 2: Алгоритм объединения всех значимых точек (Универсальный)

Если задача стоит шире — объединить две таблицы с разными данными в одну шкалу, рекомендуется использовать четырехшаговый алгоритм «сбора точек перелома».

Шаг 1: Сбор всех границ. Выбираем все даты начала из Таблицы 1 и все даты начала из Таблицы 2. Объединяем их через ОБЪЕДИНИТЬ ВСЕ во временную таблицу.

Шаг 2: Получение уникальных моментов времени. Схлопываем дубликаты дат с помощью СГРУППИРОВАТЬ ПО. Теперь у нас есть полный список моментов, когда в системе происходило любое изменение (хоть по первой таблице, хоть по второй).

Шаг 3: Формирование «пустых» интервалов. Используем метод самосоединения, описанный выше, чтобы превратить список дат в список периодов [ДатаНачала, ДатаОкончания].

Шаг 4: Наполнение данными. К полученной сетке периодов левым соединением подтягиваем данные из исходных таблиц. Условие соединения будет выглядеть так:


ЛЕВОЕ СОЕДИНЕНИЕ ИсходнаяТаблица1 КАК Таб1
ПО Сетка.Сотрудник = Таб1.Сотрудник
И Сетка.ДатаНачала >= Таб1.ДатаНачала
И Сетка.ДатаОкончания <= Таб1.ДатаОкончания

Важно помнить: если в исходных таблицах периоды могут иметь «дырки», условие соединения нужно проектировать аккуратно, возможно, используя дополнительные временные таблицы для хранения «срезов последних» на каждую точку перелома.

Особенности работы в конфигурации ЗУП 3.1 КОРП

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

  1. Использование функций общих модулей. В ЗУП 3.1 предусмотрены программные инструменты для таких операций. Например, методы в модуле ЗарплатаКадрыОбщие позволяют создавать временные таблицы с периодами автоматически.
  2. Проблема «0:0:59». Если вы видите такое время, проанализируйте, как записываются данные в регистр. Часто это результат работы механизмов вытеснения. При сравнении дат в запросе старайтесь приводить их к началу дня через НАЧАЛОПЕРИОДА, если точное время не имеет значения для учета.
  3. Менеджер временных таблиц. При написании сложных отчетов в ЗУП КОРП всегда используйте менеджер временных таблиц. Это позволит вам разбить один гигантский запрос на 5-6 простых и понятных шагов, что значительно облегчит отладку интервалов — поможет консоль запросов и СКД для тестирования алгоритмов.

Современный подход: Оконные функции (Платформа 8.3.21+)

Проанализируем ситуацию с развитием платформы. Начиная с версии 8.3.21, в языке запросов появились оконные функции, которые позволяют решить задачу получения «следующей даты» без тяжелого самосоединения LEFT JOIN.

Рассмотрим пример использования функции LEAD (в 1С это реализуется через СЛЕДУЮЩЕЕ в контексте оконных функций, если это поддерживается синтаксисом конкретной версии или через СКД):

В системе компоновки данных (СКД) можно использовать выражение:

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

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

Важный совет: Если вы работаете на старых версиях платформы или пишите код для тиражного решения с неопределенной версией сервера 1С, придерживайтесь Метода 1, так как он является наиболее совместимым и стабильным.

← На главную