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