Почему не совпадают подразделения и должности при выводе сотрудников по штатному расписанию в 1С?

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

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

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

Рассмотрим фрагмент кода, в котором формируются два последовательных запроса: первый выбирает записи регистра сведений ШтатноеРасписаниеОрганизаций, а второй в цикле пытается найти сотрудников по выбранным подразделениям и должностям:


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

В приведенном алгоритме допущено сразу несколько критических ошибок, приводящих к искажению данных:

  1. Несоответствие полей выборки и условий отбора: в секции ВЫБРАТЬ второго запроса запрашиваются поля Должность.Код и ПодразделениеОрганизации.Код, тогда как в секции ГДЕ условие строится по полям ТекущаяДолжностьОрганизации.Код и ТекущееПодразделениеОрганизации.Код. В типовых конфигурациях реквизиты ПодразделениеОрганизации и Должность фиксируют место работы сотрудника на момент приема по трудовому договору, а реквизиты с префиксом Текущее/Текущая содержат сведения о текущем месте работы после кадровых переводов. Из-за этого выводимые в отчет коды расходятся с кодами отбора.
  2. Эффект «залипания» параметров области табличного документа: объект ОбластьДанные.Параметры не очищается перед следующей итерацией внешнего цикла. Если по позиции штатного расписания сотрудники отсутствуют (вакантная ставка) и запрос возвращает пустую выборку, цикл Пока ВыборкаСотр.Следующий() не выполняется ни разу. В результате строка макета заполняется значениями сотрудника из предыдущей итерации.
  3. Вывод области вне цикла по сотрудникам: метод ТабДок.Вывести(ОбластьДанные, 1) расположен за пределами цикла выборки сотрудников. Если на одну штатную позицию принято несколько человек, в табличный документ попадет только последний из них, а остальные будут перезаписаны.
  4. Сравнение по кодам вместо ссылок: использование строковых кодов в параметрах запроса и обращения через точку (.Код) в блоке ГДЕ порождают неявные соединения таблиц в СУБД, снижают производительность и приводят к ошибкам при наличии лидирующих нулей или дублей кодов в справочниках.
  5. Запрос внутри цикла: выполнение отдельного запроса к базе данных на каждой итерации обхода штатного расписания катастрофически замедляет формирование отчета на реальных базах.

Способ 1. Исправление логики заполнения параметров и условий запроса

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


Выборка = ЗапросШт.Выполнить().Выбрать();
ТекущееПодразделение = Неопределено;

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

    // Параметры запроса передаем по ссылкам, а не по кодам
    ЗапросСотр.УстановитьПараметр("Подразделение", Выборка.ПодразделениеОрганизации);
    ЗапросСотр.УстановитьПараметр("Должность", Выборка.Должность);
    
    ВыборкаСотр = ЗапросСотр.Выполнить().Выбрать();
    
    Если ВыборкаСотр.Количество() = 0 Тогда
        // Обрабатываем вакантную позицию
        ЗаполнитьЗначенияСвойств(ОбластьДанные.Параметры, Выборка);
        ОбластьДанные.Параметры.ФИО = "<Вакансия>";
        ОбластьДанные.Параметры.Дкод = "";
        ОбластьДанные.Параметры.Пкод = "";
        ТабДок.Вывести(ОбластьДанные);
    Иначе
        Пока ВыборкаСотр.Следующий() Цикл
            // Сначала переносим данные штатной позиции, затем данные сотрудника
            ЗаполнитьЗначенияСвойств(ОбластьДанные.Параметры, Выборка);
            ЗаполнитьЗначенияСвойств(ОбластьДанные.Параметры, ВыборкаСотр);
            ТабДок.Вывести(ОбластьДанные);
        КонецЦикла;
    КонецЕсли;
КонецЦикла;

В тексте запроса по сотрудникам при этом необходимо запрашивать именно те поля, по которым выполняется отбор:


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

Способ 2. Оптимальное решение через единый запрос с левым соединением

Разберем по шагам наиболее эффективный подход с точки зрения архитектуры 1С. Объединим выборку штатного расписания и данных сотрудников в один запрос с помощью ЛЕВОЕ СОЕДИНЕНИЕ. Это полностью исключит запросы в цикле, решит проблему зависания старых значений (для вакансий поля сотрудников вернут NULL) и обеспечит максимальную производительность.


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

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

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

Преимущества такого подхода:

  1. Связь таблиц строится исключительно по ссылкам (ПодразделениеОрганизации и ТекущееПодразделениеОрганизации), что исключает влияние несовпадающих или пустых кодов.
  2. Для группировки по подразделениям используется стандартная конструкция ИТОГИ ПО платформы 1С:Предприятие, что устраняет необходимость вручную отслеживать смену подразделения во временных переменных.
  3. Если на штатной позиции нет сотрудников, функция ЕСТЬNULL подставляет значение по умолчанию, а параметры табличного документа перезаписываются на каждой строке выборки без риска дублирования информации.
← На главную