Как найти предыдущую должность сотрудника в 1С:ЗУП с помощью запроса?

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

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

Способ 1: Использование временных таблиц и среза последних (для ЗУП 2.5 и старых структур)

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

Сначала мы формируем временную таблицу втСотрудники, куда попадают данные о текущих назначениях. Важно учитывать, что в ЗУП 2.5 поля ДолжностьЗавершения и ПериодЗавершения используются для краткосрочных кадровых изменений, поэтому мы применяем конструкцию ВЫБОР.


ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
    РаботникиОрганизаций.Сотрудник КАК Сотрудник,
    РаботникиОрганизаций.Организация,
    ВЫБОР
        КОГДА РаботникиОрганизаций.ПериодЗавершения > ДАТАВРЕМЯ(1, 1, 1)
        ТОГДА РаботникиОрганизаций.ДолжностьЗавершения
        ИНАЧЕ РаботникиОрганизаций.Должность
    КОНЕЦ КАК Должность,
    РаботникиОрганизаций.Период КАК Период
ПОМЕСТИТЬ втСотрудники
ИЗ
    РегистрСведений.РаботникиОрганизаций КАК РаботникиОрганизаций
ГДЕ
    РаботникиОрганизаций.Период МЕЖДУ &ПериодНачало И &ПериодОкончание
;

Далее нам необходимо найти предыдущую запись. Для этого мы создаем еще одну временную таблицу со всей историей и соединяем её с основной по условию втСотрудники.Период > Вт_ВсеДолжности.Период. Чтобы не получить «кашу» из всех старых должностей (дублирование строк), мы используем вложенный запрос для поиска максимальной даты среди всех прошлых записей.


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

Важный нюанс: обратите внимание на условие Р.Период < втСотрудники.Период. Если использовать ДОБАВИТЬКДАТЕ(Период, ДЕНЬ, -1), можно пропустить изменения, произошедшие в течение одного дня. Использование строгого неравенства и функции МАКСИМУМ гарантирует получение именно последней записи перед текущей.

Способ 2: Работа с интервальными регистрами в ЗУП 3.1

В современных редакциях ЗУП 3.1 архитектура хранения кадровых данных изменилась. Теперь широко используются интервальные регистры, такие как КадроваяИсторияСотрудниковИнтервальный. Каждая запись в таком регистре уже содержит поля ДатаНачала и ДатаОкончания. Это значительно упрощает поиск предыдущей должности, так как нам не нужно искать максимум по всему регистру — достаточно найти запись, «стыкующуюся» по датам.

Проанализируем ситуацию: для текущей записи предыдущая будет иметь ДатаОкончания, равную ДатаНачала - 1 секунда (или просто находящуюся вплотную). Рассмотрим пример кода:


ВЫБРАТЬ
    История.Сотрудник КАК Сотрудник,
    История.Должность КАК ТекущаяДолжность,
    История.ДатаНачала КАК ДатаНачала,
    ДОБАВИТЬКДАТЕ(История.ДатаНачала, СЕКУНДА, -1) КАК МоментПредыдущейЗаписи
ПОМЕСТИТЬ ВТСотрудники
ИЗ
    РегистрСведений.КадроваяИсторияСотрудниковИнтервальный КАК История
ГДЕ
    &Период МЕЖДУ История.ДатаНачала И История.ДатаОкончания
;

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

Преимущество этого метода в производительности. Использование индексированных полей ДатаНачала и ДатаОкончания в интервальных регистрах работает быстрее, чем вложенные подзапросы с агрегатными функциями МАКСИМУМ.

Способ 3: Оконные функции (LAG) для платформы 8.3.21+

Если ваша база работает на платформе версии 8.3.21 и выше, мы можем использовать мощный механизм оконных функций. Функция LAG позволяет получить значение из предыдущей строки результирующего набора без повторного обращения к таблице через JOIN.

Посмотрим, как это выглядит в запросе:


ВЫБРАТЬ
    История.Сотрудник,
    История.Период,
    История.Должность КАК ТекущаяДолжность,
    LAG(История.Должность) НАД (УПОРЯДОЧИТЬ ПО История.Сотрудник, История.Период) КАК ПредыдущаяДолжность
ИЗ
    РегистрСведений.РаботникиОрганизаций КАК История

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

Типичные ошибки и пути их решения

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

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

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

Таким образом, мы проанализировали ситуацию с разных сторон. Выбор конкретного метода зависит от вашей версии конфигурации и требований к производительности. Для старых систем используйте корректное соединение с МАКСИМУМ(Период), для новых — интервальные регистры или оконные функции.

← На главную