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

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

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

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

Выясним причину ошибки: почему конструкция ПЕРВЫЕ 1 не работает в соединении

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


ЛЕВОЕ СОЕДИНЕНИЕ (
    ВЫБРАТЬ ПЕРВЫЕ 1 
        ДанныеДляПодбораСотрудников.Должность КАК Должность, 
        ДанныеДляПодбораСотрудников.Начало КАК Начало, 
        ДанныеДляПодбораСотрудников.Подразделение КАК Подразделение, 
        ДанныеДляПодбораСотрудников.Сотрудник КАК Сотрудник 
    ИЗ 
        РегистрСведений.ДанныеДляПодбораСотрудников КАК ДанныеДляПодбораСотрудников 
    УПОРЯДОЧИТЬ ПО 
        Начало УБЫВ
) КАК ДолжностьИПодразделение 
ПО ТЧСотрудники.Сотрудник = ДолжностьИПодразделение.Сотрудник

При выполнении такого запроса данные по должности и подразделению заполняются только для одного сотрудника (или вовсе остаются пустыми). Выясним, почему так происходит.

Главное ограничение языка запросов 1С: в нем отсутствуют коррелированные подзапросы (подзапросы, выполняющиеся индивидуально для каждой строки внешнего запроса). СУБД рассчитывает вложенный запрос один раз перед выполнением соединения. Инструкция ПЕРВЫЕ 1 ограничивает выборку ровно до одной строки со всей таблицы регистра. В итоге во вложенном запросе останется лишь одна запись по какому-то одному сотруднику с самой поздней датой, и только с ней будет выполняться дальнейшее левое соединение.

Правильное решение: использование пакетного запроса и временных таблиц

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

Разберем по шагам создание пакета запросов:

  1. Формируем исходный список сотрудников: помещаем данные табличной части документа во временную таблицу ВТ_Док.
  2. Находим максимальную дату записи для каждого сотрудника: выбираем из регистра сведений максимальную дату начала (МАКСИМУМ(Начало)) с группировкой по полю Сотрудник, ограничивая выборку условием вхождения в список сотрудников из первой временной таблицы. Результат сохраняем в ВТ_ПоследняяДатаДолжности.
  3. Получаем реквизиты на найденную дату: связываем полученные максимальные даты обратно с физической таблицей регистра сведений одновременно по двум полям (Сотрудник и Начало). Это позволяет срезервировать ровно по одной актуальной строке на каждого человека и поместить должность и подразделение в ВТ_Должность.
  4. Формируем итоговую выборку: соединяем исходную таблицу сотрудников ВТ_Док с полученной таблицей актуальных должностей ВТ_Должность и срезом последних регистра ФИО.

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


ВЫБРАТЬ 
    РаботаВВыходныеИПраздничныеДниСотрудники.Дата КАК ДатаВыхода, 
    СУММА(РаботаВВыходныеИПраздничныеДниСотрудники.ОтработаноЧасов) КАК ОтработаноЧасов, 
    РаботаВВыходныеИПраздничныеДниСотрудники.Сотрудник КАК Сотрудник 
ПОМЕСТИТЬ ВТ_Док 
ИЗ 
    Документ.РаботаВВыходныеИПраздничныеДни.Сотрудники КАК РаботаВВыходныеИПраздничныеДниСотрудники 
ГДЕ 
    РаботаВВыходныеИПраздничныеДниСотрудники.Ссылка = &Ссылка 
СГРУППИРОВАТЬ ПО 
    РаботаВВыходныеИПраздничныеДниСотрудники.Сотрудник, 
    РаботаВВыходныеИПраздничныеДниСотрудники.Дата;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ 
    МАКСИМУМ(ДанныеДляПодбораСотрудников.Начало) КАК МаксимальноеНачало, 
    ДанныеДляПодбораСотрудников.Сотрудник КАК Сотрудник 
ПОМЕСТИТЬ ВТ_ПоследняяДатаДолжности 
ИЗ 
    РегистрСведений.ДанныеДляПодбораСотрудников КАК ДанныеДляПодбораСотрудников 
ГДЕ 
    ДанныеДляПодбораСотрудников.Сотрудник В (ВЫБРАТЬ ВТ_Док.Сотрудник ИЗ ВТ_Док КАК ВТ_Док) 
СГРУППИРОВАТЬ ПО 
    ДанныеДляПодбораСотрудников.Сотрудник;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ 
    ВТ_ПоследняяДатаДолжности.МаксимальноеНачало КАК Начало, 
    ВТ_ПоследняяДатаДолжности.Сотрудник КАК Сотрудник, 
    ДанныеДляПодбораСотрудников.Должность КАК Должность, 
    ДанныеДляПодбораСотрудников.Подразделение КАК Подразделение 
ПОМЕСТИТЬ ВТ_Должность 
ИЗ 
    ВТ_ПоследняяДатаДолжности КАК ВТ_ПоследняяДатаДолжности 
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеДляПодбораСотрудников КАК ДанныеДляПодбораСотрудников 
        ПО ВТ_ПоследняяДатаДолжности.Сотрудник = ДанныеДляПодбораСотрудников.Сотрудник 
        И ВТ_ПоследняяДатаДолжности.МаксимальноеНачало = ДанныеДляПодбораСотрудников.Начало;

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

Особенности работы с датой документа и оптимизация производительности

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

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


ГДЕ 
    ДанныеДляПодбораСотрудников.Начало <= &ДатаДокумента

2. Индексация временных таблиц: При работе с большими объемами данных (сотни и тысячи сотрудников) временные таблицы, участвующие в левых соединениях, рекомендуется индексировать. Для этого в конце создания временной таблицы указывается ключевое слово ИНДЕКСИРОВАТЬ ПО:


ПОМЕСТИТЬ ВТ_Док 
ПОЛЕЗНЫЙ ИНДЕКС: ИНДЕКСИРОВАТЬ ПО Сотрудник

3. Альтернативный вариант в программах на базе ЗУП 3.1 / ERP: В программном коде типовых конфигураций для массового получения кадровых сведений можно использовать встроенный механизм платформенных модулей, такой как КадровыйУчет.КадровыеДанныеСотрудников(), который берет на себя формирование оптимальных запросов с учетом истории кадровых перемещений.

← На главную