Как найти дату вступления в последнюю должность в 1С:ЗУП 2.5 без запросов в цикле?

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

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

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

Проблема: Почему запрос в цикле — это плохо?

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

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

Посмотрим на пример «плохого» кода, который нам нужно оптимизировать:


Для Каждого Стр Из Выборка Цикл
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ РАЗРЕШЕННЫЕ
                   | РаботникиОрганизаций.Период КАК Период,
                   | РаботникиОрганизаций.Должность
                   |ИЗ
                   | РегистрСведений.РаботникиОрганизаций КАК РаботникиОрганизаций
                   |ГДЕ
                   | РаботникиОрганизаций.Сотрудник = &Сотрудник
                   |УПОРЯДОЧИТЬ ПО Период УБЫВ";
    Запрос.УстановитьПараметр("Сотрудник", Стр.Сотрудник);
    // Далее следует перебор таблицы значений...
КонецЦикла;

Наша цель — перенести всю эту логику в один запрос, используя мощь языка SQL и временных таблиц 1С.

Алгоритм «Граничного значения»: решаем задачу эффективно

Чтобы найти дату начала непрерывного стажа в текущей должности, мы применим метод исключения периодов предыдущих должностей. Разберем логику по шагам, как будто мы проектируем этот запрос вместе.

Шаг 1. Получение актуального состояния

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

Шаг 2. Поиск последней «чужой» должности

Теперь самое интересное. Нам нужно найти дату самого последнего события, когда сотрудник занимал любую другую должность, отличную от текущей. Мы возьмем нашу ВТ_ТекущиеДанные и соединим её с физической таблицей регистра РаботникиОрганизаций по сотруднику, но с условием, что Должность в регистре НЕ РАВНА должности из временной таблицы.

Используя агрегатную функцию МАКСИМУМ(Период), мы получим дату последнего дня работы на старой должности.

Шаг 3. Получение финальной даты

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

Практическая реализация запроса

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


ВЫБРАТЬ
    Работники.Сотрудник КАК Сотрудник,
    Работники.Должность КАК Должность,
    Работники.Период КАК ПериодСреза
ПОМЕСТИТЬ ВТ_ТекущиеДанные
ИЗ
    РегистрСведений.РаботникиОрганизаций.СрезПоследних(&Период, ) КАК Работники
;

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

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

Разбор нюансов и важных моментов

Проанализируем, почему этот запрос работает корректно в сложных случаях:

  1. Если должностей не менялось: В этом случае во второй временной таблице МАКСИМУМ(Период) вернет NULL (который мы превратим в 01.01.0001 через ЕСТЬNULL). Тогда в финальном шаге МИНИМУМ(Период) вернет самую первую запись — дату приема на работу.
  2. Если были перемещения без смены должности: Представим, что 01.01 сотрудник стал «Механиком», а 01.03 его переместили в другой цех на ту же должность «Механик». Наш запрос на втором шаге проигнорирует запись от 01.03, так как должность там совпадает. Границей останется дата до 01.01, и в итоге МИНИМУМ вернет именно 01.01. Это именно то, что требовалось автору.
  3. Сотрудник или Физлицо: В Сообщении 12 форума справедливо отмечено, что в ЗУП 2.5 кадровый учет ведется в разрезе сотрудников. Если использовать Физлицо, можно ошибочно смешать историю по разным трудовым договорам (например, по основному месту работы и внутреннему совместительству). Всегда используйте Сотрудник для подобных кадровых отчетов.

Советы по оптимизации производительности

Если в вашей организации работают десятки тысяч сотрудников, обратите внимание на следующие рекомендации:

Индексация временных таблиц: Добавьте ключевое слово ИНДЕКСИРОВАТЬ ПО для поля Сотрудник во временных таблицах. Это значительно ускорит операции ЛЕВОЕ СОЕДИНЕНИЕ на больших объемах данных.

Ограничение по периоду: В виртуальной таблице СрезПоследних всегда передавайте параметр &Период. Работа со срезом на «неопределенную дату» заставляет систему выполнять лишние вычисления.

Использование Категорий и Классов условий труда: Как видно из Сообщения 6, автор также пытался соединять данные с категориями объектов и классами условий труда. Помните, что такие соединения лучше делать в самом последнем пакете запроса, когда список сотрудников уже ограничен и дата вступления в должность найдена. Это избавит систему от лишней работы по перебору старых записей из дополнительных регистров.

Таким образом, мы выяснили, что перевод логики из циклов в пакетный запрос не только ускоряет выполнение программы в десятки раз, но и делает код более надежным и соответствующим стандартам разработки 1С. Рассмотренный метод «граничного значения» является универсальным и может быть применен для поиска даты любого изменения состояния: смены подразделения, изменения графика работы или графика начислений.

← На главную