Как в запросе 1С объединить даты приема и увольнения сотрудника в одну строку из регистра состояний?

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

При проектировании отчетов и обработок в среде «1С:Предприятие 8.3» разработчики часто сталкиваются с необходимостью транспонировать («свернуть») строки одной таблицы. Классический пример такой задачи возникает при работе с кадровыми данными: в регистре сведений записи о приемах на работу и увольнениях хранятся в виде отдельных строк (разных записей), а нам необходимо получить компактную таблицу, где для каждого сотрудника в одной строке будут выведены и дата приема, и дата увольнения.

В этой статье мы подробно разберем, как решить эту задачу с помощью языка запросов 1С, проанализируем возможные подводные камни (такие как повторные приемы сотрудников на работу) и рассмотрим готовые профессиональные инструменты, которые предлагает конфигурация «1С:Зарплата и управление персоналом 3.1» (ЗУП 3.1).

Постановка задачи и анализ структуры данных

Представим, что у нас есть регистр сведений ДанныеСостоянийСотрудников со следующей структурой:

Если мы сделаем простой выбор из этого регистра, то получим две разные строки для одного и того же сотрудника:

  1. Сотрудник: Иванов, Состояние: Работа, ДатаНачала: 01.01.2023
  2. Сотрудник: Иванов, Состояние: Увольнение, ДатаНачала: 15.08.2023

Наша цель — получить одну строку: Иванов | 01.01.2023 | 15.08.2023.

Давайте пошагово разберем три основных метода решения этой задачи, начиная от чистого SQL-подхода в рамках языка запросов 1С и заканчивая специализированными механизмами ЗУП 3.1.


Решение 1. Соединение таблицы самой с собой (Self-join) с условием связи

Это классический и наиболее гибкий способ решения задачи на уровне СУБД. Нам необходимо виртуально разделить исходный регистр на две независимые таблицы: таблицу «Приемы» (где состояние равно «Работа») и таблицу «Увольнения» (где состояние равно «Увольнение»), а затем соединить их по сотруднику.

Однако здесь кроется главная ловушка кадрового учета (о которой справедливо упомянули в сообщении 2 на форуме): сотрудник может увольняться и приниматься на работу несколько раз за свою историю работы в компании. Если мы просто соединим приемы с увольнениями по равенству сотрудника, СУБД выполнит декартово произведение, сопоставив каждый прием со всеми увольнениями этого сотрудника (включая будущие или прошлые).

Чтобы избежать этой проблемы, мы применим так называемое тета-соединение (соединение по условию неравенства) с последующей группировкой по минимальной дате. Мы укажем системе, что дата увольнения должна быть строго больше или равна дате приема, а среди всех возможных дат увольнения выберем самую раннюю (минимальную). Это позволит нам гарантированно сопоставить каждый прием с его «родным» последующим увольнением.

Проанализируем готовый код запроса:


ВЫБРАТЬ
    Приемы.Сотрудник КАК Сотрудник,
    Приемы.ДатаНачала КАК ДатаПриема,
    МИНИМУМ(Увольнения.ДатаНачала) КАК ДатаУвольнения
ИЗ
    (ВЫБРАТЬ
        Сотрудник КАК Сотрудник,
        ДатаНачала КАК ДатаНачала
    ИЗ
        РегистрСведений.ДанныеСостоянийСотрудников КАК ДанныеСостоянийСотрудников
    ГДЕ
        Состояние = ЗНАЧЕНИЕ(Перечисление.СостоянияСотрудника.Работа)) КАК Приемы
    ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
        Сотрудник КАК Сотрудник,
        ДатаНачала КАК ДатаНачала
    ИЗ
        РегистрСведений.ДанныеСостоянийСотрудников КАК ДанныеСостоянийСотрудников
    ГДЕ
        Состояние = ЗНАЧЕНИЕ(Перечисление.СостоянияСотрудника.Увольнение)) КАК Увольнения
    ПО Приемы.Сотрудник = Увольнения.Сотрудник
        И Увольнения.ДатаНачала >= Приемы.ДатаНачала

СГРУППИРОВАТЬ ПО
    Приемы.Сотрудник,
    Приемы.ДатаНачала

Как это работает шаг за шагом:

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

Решение 2. Свертка через конструкцию «ВЫБОР» (без соединений)

Если специфика вашей задачи допускает предположение, что нам нужно получить только последние (актуальные) даты кадровых событий по каждому сотруднику без необходимости выстраивать полную историю цепочек «прием-увольнение», то можно применить гораздо более производительный метод. Этот метод не использует тяжелые операции соединения таблиц (JOIN), которые нагружают сервер СУБД.

Вместо этого мы обходим таблицу один раз, распределяя даты по колонкам с помощью условного оператора ВЫБОР, и сворачиваем строки через агрегатную функцию МАКСИМУМ:


ВЫБРАТЬ
    РегДанные.Сотрудник КАК Сотрудник,
    МАКСИМУМ(ВЫБОР
            КОГДА РегДанные.Состояние = ЗНАЧЕНИЕ(Перечисление.СостоянияСотрудника.Работа)
                ТОГДА РегДанные.ДатаНачала
            ИНАЧЕ NULL
        КОНЕЦ) КАК ДатаПриема,
    МАКСИМУМ(ВЫБОР
            КОГДА РегДанные.Состояние = ЗНАЧЕНИЕ(Перечисление.СостоянияСотрудника.Увольнение)
                ТОГДА РегДанные.ДатаНачала
            ИНАЧЕ NULL
        КОНЕЦ) КАК ДатаУвольнения
ИЗ
    РегистрСведений.ДанныеСостоянийСотрудников КАК РегДанные

СГРУППИРОВАТЬ ПО
    РегДанные.Сотрудник

Преимущества данного подхода:

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


Решение 3. Использование встроенных механизмов конфигурации ЗУП 3.1

Если вы ведете разработку непосредственно в конфигурации «1С:Зарплата и управление персоналом 3.1», то писать прямые запросы к сырым регистрам состояний считается дурным тоном. Конфигурация уже содержит оптимизированные регистры и готовые функции API, разработанные методистами фирмы «1С».

Вариант А. Использование специального регистра «ТекущиеКадровыеДанныеСотрудников»

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

Запрос к нему предельно прост, быстр и не требует никаких группировок:


ВЫБРАТЬ
    ТекущиеКадры.Сотрудник КАК Сотрудник,
    ТекущиеКадры.ДатаПриема КАК ДатаПриема,
    ТекущиеКадры.ДатаУвольнения КАК ДатаУвольнения
ИЗ
    РегистрСведений.ТекущиеКадровыеДанныеСотрудников КАК ТекущиеКадры

Вариант Б. Программный интерфейс (API) общего модуля «КадровыйУчет»

Для получения актуальных кадровых данных в программном коде рекомендуется использовать готовый метод конфигурации. Это гарантирует, что логика вашего решения не сломается при очередном обновлении ЗУП разработчиками фирмы «1С» и автоматически учтет настройки прав доступа (RLS).

Рассмотрим пример программного вызова этой функции:


// Формируем массив сотрудников, по которым нужны данные
МассивСотрудников = Новый Массив;
МассивСотрудников.Добавить(ВыбранныйСотрудник);

// Задаем список требуемых кадровых полей
КадровыеПоля = Новый Массив;
КадровыеПоля.Добавить("ДатаПриема");
КадровыеПоля.Добавить("ДатаУвольнения");

// Получаем таблицу значений с готовыми данными на текущую дату
ТаблицаКадровыхДанных = КадровыйУчет.КадровыеДанныеСотрудников(Истина, МассивСотрудников, КадровыеПоля);

Для Каждого СтрокаДанных Из ТаблицаКадровыхДанных Цикл
    Сообщить("Сотрудник: " + СтрокаДанных.Сотрудник 
        + ", Принят: " + СтрокаДанных.ДатаПриема 
        + ", Уволен: " + СтрокаДанных.ДатаУвольнения);
КонецЦикла;

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

Резюме

Для решения задачи объединения дат приема и увольнения в одну строку мы рассмотрели три подхода:

  1. Левое соединение таблицы с собой (Self-join) — универсальный вариант для написания сложных отчетов по истории изменений на чистой платформе 1С. Требует обязательного контроля дат с использованием конструкции МИНИМУМ.
  2. Группировка с конструкцией ВЫБОР — отличный быстродействующий вариант, если необходимо получить только последние актуальные даты кадровых событий.
  3. Использование стандартных механизмов ЗУП 3.1 (регистр ТекущиеКадровыеДанныеСотрудников или программный интерфейс КадровыйУчет.КадровыеДанныеСотрудников) — наиболее предпочтительный, надежный и оптимизированный путь для профессиональных разработчиков в среде ЗУП.
← На главную