В работе с кадровыми данными в 1С:ЗУП часто возникает задача получить самое последнее событие для физического лица. Сложность в том, что одно и то же физлицо может быть оформлено в программе как несколько сотрудников — например, по основному месту работы и по внутреннему совместительству. Стандартный механизм получения среза последних данных вернет несколько записей, по одной на каждого сотрудника. Разберем, как с помощью запроса получить одну, самую последнюю запись для физического лица, независимо от того, сколькими сотрудниками оно представлено.
Для начала выясним причину такого поведения. Виртуальная таблица регистра сведений СрезПоследних предназначена для получения актуальных данных на определенную дату. Ключевой момент в ее работе — она возвращает по одной записи на каждую уникальную комбинацию значений измерений регистра.
У регистра сведений КадроваяИсторияСотрудников в ЗУП три основных измерения:
СотрудникГоловнаяОрганизацияФизическоеЛицоКогда мы делаем отбор только по измерению ФизическоеЛицо, в регистре все еще могут существовать несколько уникальных комбинаций измерений для этого физлица (например, "Иванов И.И. (основной)" и "Иванов И.И. (совместитель)"). В результате запрос вернет столько записей, сколько сотрудников связано с этим физлицом, что не соответствует нашей задаче.
Часто начинающие разработчики пытаются решить проблему с помощью агрегатной функции МАКСИМУМ() напрямую в запросе к срезу, но делают это неверно. Посмотрим на типичную ошибку:
ВЫБРАТЬ
КадроваяИсторияСотрудниковСрезПоследних.ФизическоеЛицо КАК ФизическоеЛицо,
КадроваяИсторияСотрудниковСрезПоследних.Подразделение КАК Подразделение,
МАКСИМУМ(КадроваяИсторияСотрудниковСрезПоследних.Период) КАК Период
ИЗ
РегистрСведений.КадроваяИсторияСотрудников.СрезПоследних(&НачалоПериода, ФизическоеЛицо = &ФизическоеЛицо) КАК КадроваяИсторияСотрудниковСрезПоследних
СГРУППИРОВАТЬ ПО
КадроваяИсторияСотрудниковСрезПоследних.ФизическоеЛицо,
КадроваяИсторияСотрудниковСрезПоследних.Подразделение
Такой запрос не решит проблему, так как группировка по полю Подразделение приведет к тому, что мы получим максимальную дату для каждой пары "Физлицо + Подразделение", а нам нужна одна последняя дата для физлица в целом.
Наиболее надежный и понятный способ эмулировать срез по одному измерению — это двухэтапный запрос с использованием временной таблицы. Логика здесь простая: сначала находим последнюю дату события для каждого физлица, а затем по этой дате получаем все остальные нужные нам данные.
Шаг 1. Находим максимальную дату для каждого физлица.
На этом шаге мы обращаемся к основной таблице регистра сведений, группируем данные по полю ФизическоеЛицо и с помощью функции МАКСИМУМ(Период) находим самую последнюю дату кадрового события. Результат помещаем во временную таблицу.
ВЫБРАТЬ
КадроваяИстория.ФизическоеЛицо КАК ФизическоеЛицо,
МАКСИМУМ(КадроваяИстория.Период) КАК Период
ПОМЕСТИТЬ ПоследниеДействия
ИЗ
РегистрСведений.КадроваяИсторияСотрудников КАК КадроваяИстория
СГРУППИРОВАТЬ ПО
КадроваяИстория.ФизическоеЛицо
Теперь во временной таблице ПоследниеДействия у нас есть по одной строке на каждое физлицо с датой его последнего кадрового события.
Шаг 2. Получаем полные данные по найденной дате.
Вторым запросом мы соединяем основную таблицу регистра КадроваяИсторияСотрудников с нашей временной таблицей ПоследниеДействия. Соединение происходит по двум полям: ФизическоеЛицо и Период. Это позволяет нам "вытащить" из основной таблицы всю строку (включая подразделение, должность и т.д.), которая соответствует найденной нами последней дате.
Полный запрос выглядит так:
// Шаг 1: Найти последние даты для каждого физлица
ВЫБРАТЬ
КадроваяИстория.ФизическоеЛицо КАК ФизическоеЛицо,
МАКСИМУМ(КадроваяИстория.Период) КАК Период
ПОМЕСТИТЬ ПоследниеДействия
ИЗ
РегистрСведений.КадроваяИсторияСотрудников КАК КадроваяИстория
СГРУППИРОВАТЬ ПО
КадроваяИстория.ФизическоеЛицо
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Соединить с основной таблицей и получить нужные поля
ВЫБРАТЬ
КадроваяИстория.ФизическоеЛицо,
КадроваяИстория.Подразделение,
КадроваяИстория.Должность,
ПоследниеДействия.Период
ИЗ
РегистрСведений.КадроваяИсторияСотрудников КАК КадроваяИстория
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПоследниеДействия КАК ПоследниеДействия
ПО КадроваяИстория.Период = ПоследниеДействия.Период
И КадроваяИстория.ФизическоеЛицо = ПоследниеДействия.ФизическоеЛицо
Важный нюанс: может возникнуть ситуация, когда у одного физлица в один и тот же день (с точностью до секунды) произошло два кадровых события (например, перевод и изменение оклада разными документами). В этом случае соединение вернет две записи. Чтобы гарантированно получить одну строку, можно использовать агрегатные функции, например МИНИМУМ() или МАКСИМУМ(), для полей ресурсов. Это гарантирует уникальность результата.
Добиться того же результата можно и без явного создания временной таблицы, используя вложенный запрос. Логически этот метод полностью идентичен предыдущему, но весь код заключается в одном запросе. Это может быть удобнее для чтения в некоторых случаях.
Внутренний (вложенный) запрос выполняет ту же функцию, что и первый шаг в предыдущем решении — находит максимальные даты. А внешний запрос соединяется с результатом работы внутреннего.
Посмотрим на пример:
ВЫБРАТЬ
КадроваяИстория.ФизическоеЛицо,
КадроваяИстория.Подразделение,
КадроваяИстория.Период
ИЗ
РегистрСведений.КадроваяИсторияСотрудников КАК КадроваяИстория
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
Т.ФизическоеЛицо КАК ФизическоеЛицо,
МАКСИМУМ(Т.Период) КАК Период
ИЗ
РегистрСведений.КадроваяИсторияСотрудников КАК Т
СГРУППИРОВАТЬ ПО
Т.ФизическоеЛицо) КАК ПоследниеЗаписи
ПО
КадроваяИстория.ФизическоеЛицо = ПоследниеЗаписи.ФизическоеЛицо
И КадроваяИстория.Период = ПоследниеЗаписи.Период
С точки зрения производительности, выбор между временной таблицей и вложенным запросом зависит от версии платформы 1С, используемой СУБД и объема данных. В некоторых случаях, особенно при работе с PostgreSQL, вариант с временной таблицей может оказаться производительнее.
Прежде чем писать собственные запросы к регистрам, стоит проверить, нет ли в конфигурации готового инструмента для решения вашей задачи. В современных конфигурациях, построенных на БСП, существуют программные интерфейсы (API) для получения различных данных.
Для работы с кадровой информацией в ЗУП можно использовать общие модули, такие как КадровыйУчет или КадровыйУчетРасширенный. Например, функция КадровыеДанныеСотрудников может вернуть таблицу значений с актуальными данными на нужную дату.
Преимущества этого подхода:
Таким образом, для решения задачи получения последнего кадрового состояния физлица мы рассмотрели два надежных способа с помощью запросов и рекомендовали всегда в первую очередь искать готовые функции в самой конфигурации. Выбор конкретного метода зависит от контекста задачи и требований к производительности.