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