При разработке отчетов или обработок в 1С:Зарплата и Управление Персоналом (ЗУП) 3.1 часто возникает задача получить срез кадровых данных (например, подразделение или должность) не на конкретную дату, а на каждый день определенного периода. Это необходимо для формирования табелей, графиков или анализа перемещений сотрудников. Рассмотрим, почему классический подход с объединением таблиц «Прием», «Перевод» и «Начальные данные» часто приводит к ошибкам, и как реализовать эту задачу правильно.
Проанализируем ситуацию, с которой сталкиваются многие разработчики. Попытка создать отдельные временные таблицы для каждого вида кадрового события (Прием, Перемещение и т.д.), а затем соединить их с календарем, — это избыточный путь. Основная проблема здесь заключается в возникновении значений NULL там, где на конкретную дату не было зафиксировано именно того события, по которому наложен фильтр.
Выясним причину: в конфигурации ЗУП 3.1 реализован механизм интервальных регистров. Регистр КадроваяИсторияСотрудниковИнтервальный уже содержит в себе «схлопнутые» данные. Если сотрудник принят в подразделение А и через год переведен в подразделение Б, в этом регистре будут две записи, которые покрывают весь период работы без разрывов. Первая запись будет иметь ДатаОкончания, равную дате перед переводом, а вторая — ДатаОкончания в далеком будущем (31.12.3999).
Важный момент: разделяя данные по ВидСобытия, мы сами создаем «дырки» в истории, которые потом героически пытаемся закрыть с помощью ОБЪЕДИНИТЬ или сложных условий ВЫБОР. Вместо этого нам нужно использовать свойство непрерывности интервального регистра.
Рассмотрим, как упростить запрос и избавиться от NULL. Нам достаточно соединить заранее подготовленную таблицу дат с регистром кадровой истории всего один раз. Посмотрим на пример оптимизированного подхода:
РегистрСведений.КадроваяИсторияСотрудниковИнтервальный по условию вхождения даты в интервал.Пример кода запроса:
ВЫБРАТЬ
Календарь.Дата КАК Период,
История.Сотрудник КАК Сотрудник,
История.Подразделение КАК Подразделение
ИЗ
ВТ_Календарь КАК Календарь
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КадроваяИсторияСотрудниковИнтервальный КАК История
ПО Календарь.Дата МЕЖДУ История.ДатаНачала И История.ДатаОкончания
ГДЕ
История.Сотрудник В (&СписокСотрудников)
Проанализируем это условие. Так как ДатаОкончания в интервальном регистре включительная, условие МЕЖДУ отрабатывает корректно. На каждый день периода для каждого сотрудника найдется ровно одна запись из регистра, и никаких NULL в поле Подразделение не возникнет, так как интервалы в ЗУП 3.1 строятся непрерывно с даты приема по дату увольнения.
Разберем более продвинутый и надежный способ. В ЗУП 3.1 архитекторы предусмотрели программный интерфейс для получения кадровых данных, который учитывает множество нюансов: исправления документов, работу по совместительству, специфику территориальных условий и т.д. Вместо написания сложного запроса вручную, мы можем воспользоваться методами общих модулей.
Наиболее эффективным для задач табеля является использование модуля УчетРабочегоВремениРасширенный (или УчетРабочегоВремени в зависимости от версии). Метод СоздатьВТДанныеУчетаВремениИСостоянийСотрудников позволяет получить временную таблицу, в которой уже будут сопоставлены даты, сотрудники, их подразделения, графики и позиции.
Рассмотрим пример вызова такой функции в коде:
ПараметрыПолученияДанных = УчетРабочегоВремени.ПараметрыДляСозданияВТДанныеУчетаВремениИСостоянийСотрудников();
ПараметрыПолученияДанных.ДатаНачала = НачалоПериода;
ПараметрыПолученияДанных.ДатаОкончания = КонецПериода;
ПараметрыПолученияДанных.СписокСотрудников = МассивСотрудников;
УчетРабочегоВремени.СоздатьВТДанныеУчетаВремениИСостоянийСотрудников(Запрос.МенеджерВременныхТаблиц, Истина, ПараметрыПолученияДанных);
После выполнения этого кода в менеджере временных таблиц вашего запроса появится таблица ВТДанныеУчетаВремениИСостоянийСотрудников. Она содержит поле Дата и все необходимые кадровые реквизиты. Это исключает необходимость вручную бороться с NULL и дублями при кадровых переводах внутри одного дня.
Часто в запросе требуется вывести не только непосредственное подразделение, но и «головное» (верхнего уровня). Рассмотрим ситуацию из вопроса автора: использование конструкции Итог.Подразделение.Родитель. Проанализируем, почему это плохо: в запросе это приводит к неявному левому соединению с таблицей справочника ПодразделенияОрганизаций. Если уровней иерархии много, производительность падает.
Для оптимизации рекомендуем использовать реквизит ГоловноеПодразделение, если он предусмотрен в вашей логике учета, или предварительно подготовить временную таблицу со срезом структуры подразделений. Если же нужно получить именно «родителя», лучше делать это через явное левое соединение во временной таблице Итог, чтобы избежать многократных обращений через точку.
При работе с большими объемами данных (более 50-100 сотрудников) генерация дат через декартово произведение (как в исходном запросе автора) становится узким местом. Рассмотрим альтернативы:
Сотрудник и Период (или Дата). Это значительно ускорит операцию СОЕДИНЕНИЕ на стороне SQL-сервера.Таким образом, мы выяснили, что для решения задачи «подразделение на каждый день» в ЗУП 3.1 не нужно собирать данные по крупицам из разных документов. Достаточно использовать интервальный регистр кадровой истории или воспользоваться мощным типовым механизмом учета рабочего времени, который сделает всю сложную работу за вас.