Как в запросе 1С получить максимальный стаж сотрудника из разных кадровых документов?

Программист Управленческий учет Сфера услуг, туризм и социальный сектор
← На главную

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

В этой статье мы подробно разберем, как правильно составить запрос для объединения данных из разных документов, избежать критических ошибок производительности и получить максимальный стаж по каждому сотруднику. Также мы проанализируем архитектурные недостатки такого подхода и рассмотрим, как решить эту задачу в соответствии со стандартами разработки 1С (Best Practices).

Анализ проблемы и разбор типичных ошибок

Рассмотрим ситуацию: в конфигурации (например, специализированном решении для ГКП — Государственных коммунальных предприятий Казахстана) разработчики добавили реквизит гкп_Лет в табличные части документов приема и перемещения. Пользователь может заполнять этот показатель в любом из документов. Наша задача — вытащить наибольшее значение этого реквизита для каждого сотрудника.

Посмотрим на исходный вариант запроса, который часто пытаются написать начинающие разработчики:


ВЫБРАТЬ
    ОбщСтаж.Сотрудник,
    ОбщСтаж.Ссылка,
    МАКСИМУМ(ОбщСтаж.гкп_Лет) КАК Лет,
    МАКСИМУМ(ОбщСтаж.ДатаДок) КАК ДатаДок
ИЗ
    ОбщСтаж КАК ОбщСтаж
СГРУППИРОВАТЬ ПО
    ОбщСтаж.Сотрудник,
    ОбщСтаж.Ссылка,
    ОбщСтаж.гкп_Лет

В данном фрагменте кода кроются сразу две серьезные ошибки:

  1. Некорректная группировка: Если мы группируем результат по полям Ссылка и гкп_Лет, то СУБД вернет уникальные комбинации для каждого документа и каждого значения стажа. Функция МАКСИМУМ в таком случае просто теряет смысл, так как группировка не схлопнет строки до одного сотрудника. Чтобы получить максимум по сотруднику, группировать нужно строго по полю Сотрудник.
  2. Декартово произведение в пакете запросов: В первой части исходного запроса автора присутствовало неявное соединение таблиц через запятую в секции ИЗ без указания условий связи. Это приводит к полному сканированию обеих таблиц и созданию огромного объема временных данных в памяти (cross join), что способно полностью заблокировать работу базы данных при наличии большого количества кадровых документов.

Решение задачи с помощью временной таблицы и объединения

Для того чтобы решить задачу корректно и оптимально с точки зрения СУБД, мы применим двухэтапный запрос. Сначала соберем все данные по стажу из документов приема и перемещения в одну временную таблицу с помощью оператора ОБЪЕДИНИТЬ ВСЕ. Затем выберем из этой таблицы данные, сгруппировав их по сотруднику и применив агрегатную функцию МАКСИМУМ.

Разберем по шагам правильную структуру запроса:


ВЫБРАТЬ
    ПриемРаботники.Сотрудник КАК Сотрудник,
    ПриемРаботники.гкп_Лет КАК Лет
ПОМЕСТИТЬ ВТ_ОбщийСтаж
ИЗ
    Документ.ПриемНаРаботуВОрганизацию.РаботникиОрганизации КАК ПриемРаботники

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ПеремещениеРаботники.Сотрудник,
    ПеремещениеРаботники.гкп_Лет
ИЗ
    Документ.КадровоеПеремещениеОрганизаций.РаботникиОрганизации КАК ПеремещениеРаботники
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ОбщийСтаж.Сотрудник КАК Сотрудник,
    МАКСИМУМ(ВТ_ОбщийСтаж.Лет) КАК МаксимальныйСтаж
ИЗ
    ВТ_ОбщийСтаж КАК ВТ_ОбщийСтаж
СГРУППИРОВАТЬ ПО
    ВТ_ОбщийСтаж.Сотрудник

Этот запрос работает быстро и возвращает точный результат: одну строку на каждого сотрудника с его максимальным зафиксированным стажем.

Внесение критически важных доработок (Проверка проведения документов)

Представленный выше запрос имеет одну уязвимость, которая в реальной базе данных приведет к получению недостоверных данных. Мы обращаемся напрямую к табличным частям документов. Но что произойдет, если кадровик создал документ-черновик, ввел туда ошибочные данные (например, опечатку в стаже: 55 лет вместо 5), а затем отменил проведение или пометил документ на удаление?

Платформа 1С хранит данные табличных частей всех документов, независимо от их статуса проведения. Если мы не наложим фильтр, то наш запрос выберет ошибочный стаж из непроведенного или удаленного документа. Чтобы этого избежать, добавим проверку статуса проведения документов:


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

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ПеремещениеРаботники.Сотрудник,
    ПеремещениеРаботники.гкп_Лет
ИЗ
    Документ.КадровоеПеремещениеОрганизаций.РаботникиОрганизации КАК ПеремещениеРаботники
ГДЕ
    ПеремещениеРаботники.Ссылка.Проведен = ИСТИНА
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ОбщийСтаж.Сотрудник КАК Сотрудник,
    МАКСИМУМ(ВТ_ОбщийСтаж.Лет) КАК МаксимальныйСтаж
ИЗ
    ВТ_ОбщийСтаж КАК ВТ_ОбщийСтаж
СГРУППИРОВАТЬ ПО
    ВТ_ОбщийСтаж.Сотрудник

Теперь наш запрос защищен от мусорных данных. Мы выбираем информацию только из официально проведенных кадровых документов.

Архитектурная альтернатива: как это должно быть реализовано (Best Practice)

Хотя написанный нами запрос решает текущую задачу, обращение напрямую к табличным частям документов для получения актуального состояния расчетов или кадров — это архитектурный антипаттерн в 1С. По мере накопления данных за несколько лет работы предприятия такие запросы начнут вызывать серьезные просадки производительности СУБД.

В типовых конфигурациях 1С для этих целей всегда используются Регистры сведений. Стаж сотрудника — это непрерывно меняющаяся величина. Правильный подход к проектированию выглядит следующим образом:

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

Проанализируем, насколько проще и производительнее выглядит правильный запрос к виртуальной таблице регистра сведений:


ВЫБРАТЬ
    СтажСотрудниковСрезПоследних.Сотрудник КАК Сотрудник,
    СтажСотрудниковСрезПоследних.КоличествоЛет КАК Лет
ИЗ
    РегистрСведений.гкп_СтажСотрудников.СрезПоследних(&Период, ) КАК СтажСотрудниковСрезПоследних

Преимущества использования регистра очевидны: СУБД мгновенно отдает актуальные данные на любую дату (благодаря индексу по измерениям регистра), платформа 1С автоматически отсекает непроведенные документы (так как они удаляют свои движения при отмене проведения), а сам код становится лаконичным, легко читаемым и быстродействующим.

← На главную