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