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