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