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