Как правильно сопоставить штатное расписание и сотрудников в запросе 1С?

Программист 1С v8.3 (Обычные формы) 1С:Зарплата и Управление Персоналом Управленческий учет IT и автоматизация бизнеса
← На главную

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

Анализ типовых ошибок в исходном коде

Проанализируем структуру алгоритма, при котором данные штатного расписания выбираются первым запросом, а затем во внешнем цикле выполняется второй запрос для поиска сотрудника по подразделению и должности.

В таком подходе кроются сразу три критические проблемы:

  1. Антипаттерн «Запрос в цикле»: Вызов метода Выполнить() внутри цикла обхода выборки создает избыточную нагрузку на СУБД (проблема N+1 запросов), что приводит к резкому падению производительности и блокировкам таблиц.
  2. Сравнение по кодам вместо ссылок: Использование условий вида Подразделение.Код = &кодПодразд и Должность.Код = &кодДолжност крайне ненадежно. Коды могут содержать лидирующие пробелы, нули, иметь разную длину или дублироваться в разных иерархиях. В платформе 1С сравнение ссылочных объектов всегда должно выполняться по их ссылкам (типа СправочникСсылка).
  3. «Залипание» данных при использовании ЗаполнитьЗначенияСвойств: Метод ЗаполнитьЗначенияСвойств() перезаписывает в целевом объекте только те свойства, которые физически присутствуют в источнике. Если должность вакантна, второй запрос возвращает пустой результат, цикл Пока ВыборкаСотр.Следующий() не выполняется ни разу, и параметры строки макета не очищаются. В результате на печать выводятся данные сотрудника из предыдущей итерации.

Решение 1. Оптимизация структуры запросов: объединение через ЛЕВОЕ СОЕДИНЕНИЕ

Выясним причину, почему не следует разделять выборку на два запроса. Наиболее эффективным и стандартизированным решением является получение всех данных в одном запросе с помощью левого соединения таблицы штатного расписания с таблицей сотрудников.

Посмотрим на пример правильного текста запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	Штатное.ПодразделениеОрганизации.Наименование КАК Подразделение,
|	Штатное.Должность.Наименование КАК Должность,
|	Штатное.а_ПроцентВредность КАК ПроцентВредность,
|	ЕСТЬNULL(Сотрудники.Наименование, ""ВАКАНТНО"") КАК ФИО,
|	ЕСТЬNULL(Сотрудники.ТарифныйРазряд.Наименование, """") КАК ТарифныйРазряд
|ИЗ
|	РегистрСведений.ШтатноеРасписаниеОрганизаций КАК Штатное
|	ЛЕВОЕ СОЕДИНЕНИЕ Справочник.СотрудникиОрганизаций КАК Сотрудники
|		ПО Штатное.ПодразделениеОрганизации = Сотрудники.ТекущееПодразделениеОрганизации
|		И Штатное.Должность = Сотрудники.ТекущаяДолжностьОрганизации
|		И (Сотрудники.ДатаУвольнения = ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0))
|УПОРЯДОЧИТЬ ПО
|	Подразделение,
|	Должность";

Выборка = Запрос.Выполнить().Выбрать();

Разберем ключевые особенности этого запроса:

  1. Связь между таблицами организована по полям Штатное.ПодразделениеОрганизации = Сотрудники.ТекущееПодразделениеОрганизации и Штатное.Должность = Сотрудники.ТекущаяДолжностьОрганизации. Сравнение идет напрямую по ссылкам, а не по строковым кодам.
  2. Функция ЕСТЬNULL(Сотрудники.Наименование, "ВАКАНТНО") автоматически подставляет значение «ВАКАНТНО», если на указанной ставке нет работающего сотрудника.
  3. Запрос выполняется всего один раз за время формирования отчета, независимо от количества строк в штатном расписании.

Решение 2. Использование актуальных регистров сведений для среза работающих сотрудников

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

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


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	Штатное.ПодразделениеОрганизации КАК Подразделение,
|	Штатное.Должность КАК Должность,
|	Штатное.а_ПроцентВредность КАК ПроцентВредность,
|	ВЫБОР
|		КОГДА Работники.Сотрудник ЕСТЬ NULL ТОГДА ""ВАКАНТНО""
|		ИНАЧЕ Работники.Сотрудник.Наименование
|	КОНЕЦ КАК ФИО
|ИЗ
|	РегистрСведений.ШтатноеРасписаниеОрганизаций КАК Штатное
|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций.СрезПоследних(&Период, ) КАК Работники
|		ПО Штатное.ПодразделениеОрганизации = Работники.ПодразделениеОрганизации
|		И Штатное.Должность = Работники.Должность
|		И (Работники.ПричинаИзмененияСостояния <> ЗНАЧЕНИЕ(Перечисление.ПричиныИзмененияСостояния.Увольнение))";

Запрос.УстановитьПараметр("Период", ТекущаяДата());
Выборка = Запрос.Выполнить().Выбрать();

Решение 3. Корректный вывод данных в табличный документ без эффекта «залипания»

Разберем по шагам, как правильно организовать вывод выборки в результирующий табличный документ, чтобы избежать переноса значений параметров из одной строки в другую.

При обходе выборки необходимо получать новую область строки из макета на каждой итерации перед заполнением параметров:


ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("МакетОтчета");
ОбластьСтрока = Макет.ПолучитьОбласть("Строка");

Пока Выборка.Следующий() Цикл
	// Переполучаем чистую область макета для каждой строки
	ОбластьСтрока = Макет.ПолучитьОбласть("Строка");
	ЗаполнитьЗначенияСвойств(ОбластьСтрока.Параметры, Выборка);
	ТабДок.Вывести(ОбластьСтрока);
КонецЦикла;

ТабДок.Показать();

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

← На главную