При разработке отчетов в программе 1С:Зарплата и Управление Персоналом 2.5 часто возникает задача: получить список сотрудников, работавших на начало определенного периода, и добавить к ним тех, кто был принят на работу в течение этого периода. Начинающие разработчики часто пытаются решить эту задачу через ПОЛНОЕ СОЕДИНЕНИЕ, что приводит к логическим ошибкам и снижению производительности. В этой статье мы разберем, почему соединение может не работать и как правильно использовать оператор ОБЪЕДИНИТЬ для решения этой задачи.
Рассмотрим ситуацию, с которой столкнулся автор темы. В исходном запросе была попытка соединить две виртуальные таблицы СрезПоследних на разные даты (&д3 и &д2) по полю Сотрудник. Основная сложность заключается в том, что условия фильтрации в секции ГДЕ при использовании ПОЛНОЕ СОЕДИНЕНИЕ работают не так, как ожидается: если условие накладывается на поле из одной таблицы, оно может «отсечь» строки из другой таблицы, превращая полное соединение во внутреннее.
Разберем типичную ошибку в коде:
ВЫБРАТЬ
РаботникиОрганизацийСрезПоследних.Сотрудник,
РаботникиОрганизацийСрезПоследних2.Сотрудник КАК Сотрудник2
ИЗ
РегистрСведений.РаботникиОрганизаций.СрезПоследних(&д3) КАК РаботникиОрганизацийСрезПоследних
ПОЛНОЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций.СрезПоследних(&д2) КАК РаботникиОрганизацийСрезПоследних2
ПО РаботникиОрганизацийСрезПоследних.Сотрудник = РаботникиОрганизацийСрезПоследних2.Сотрудник
ГДЕ
РаботникиОрганизацийСрезПоследних2.Период МЕЖДУ &д3 И &д2
В данном случае, если во второй таблице (на дату &д2) запись не будет найдена или не попадет в период, то вся строка (включая данные из первой таблицы) может быть исключена из результата из-за фильтра в ГДЕ. Именно поэтому опытные разработчики рекомендуют переносить условия из ГДЕ в параметры виртуальной таблицы или использовать объединение.
Наиболее эффективным и понятным способом решения данной задачи является использование оператора ОБЪЕДИНИТЬ. Вместо того чтобы пытаться «склеить» данные по горизонтали, мы выбираем два независимых набора данных и выводим их один под другим.
Шаг 1. Выбор сотрудников на начальную дату. Мы берем срез последних на дату &д3 и фильтруем по нужному подразделению.
Шаг 2. Выбор принятых сотрудников за период. Мы берем срез последних на конечную дату &д2, но добавляем условие, что период этой записи (дата кадрового события) находится внутри интересующего нас интервала.
Рассмотрим итоговый код, который был предложен в обсуждении:
ВЫБРАТЬ
РаботникиОрганизацийСрезПоследних.Сотрудник,
РаботникиОрганизацийСрезПоследних.ПричинаИзмененияСостояния,
РаботникиОрганизацийСрезПоследних.ПодразделениеОрганизации,
РаботникиОрганизацийСрезПоследних.Период
ИЗ
РегистрСведений.РаботникиОрганизаций.СрезПоследних(&д3, ) КАК РаботникиОрганизацийСрезПоследних
ГДЕ
РаботникиОрганизацийСрезПоследних.ПодразделениеОрганизации = &Подр2
ОБЪЕДИНИТЬ
ВЫБРАТЬ
РаботникиОрганизацийСрезПоследних.Сотрудник,
РаботникиОрганизацийСрезПоследних.ПричинаИзмененияСостояния,
РаботникиОрганизацийСрезПоследних.ПодразделениеОрганизации,
РаботникиОрганизацийСрезПоследних.Период
ИЗ
РегистрСведений.РаботникиОрганизаций.СрезПоследних(&д2) КАК РаботникиОрганизацийСрезПоследних
ГДЕ
РаботникиОрганизацийСрезПоследних.ПодразделениеОрганизации = &Подр2
И РаботникиОрганизацийСрезПоследних.Период МЕЖДУ &д3 И &д2
И РаботникиОрганизацийСрезПоследних.ПричинаИзмененияСостояния = &ПричинаИзмененияСостояния
Хотя предложенное выше решение рабочее, его можно значительно ускорить. В 1С использование секции ГДЕ для виртуальных таблиц (таких как СрезПоследних) считается неоптимальным. Проанализируем причину: при использовании ГДЕ система сначала получает все последние записи из базы данных, а потом фильтрует их. Если в базе десятки тысяч сотрудников, это создаст колоссальную нагрузку.
Правильный подход: передавать фильтры напрямую в параметры виртуальной таблицы. В этом случае SQL-запрос будет содержать фильтрацию сразу при выборке данных.
Оптимизируем наш запрос:
ВЫБРАТЬ
Работники.Сотрудник,
Работники.ПричинаИзмененияСостояния,
Работники.ПодразделениеОрганизации,
Работники.Период
ИЗ
РегистрСведений.РаботникиОрганизаций.СрезПоследних(&д3, ПодразделениеОрганизации = &Подр2) КАК Работники
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Работники.Сотрудник,
Работники.ПричинаИзмененияСостояния,
Работники.ПодразделениеОрганизации,
Работники.Период
ИЗ
РегистрСведений.РаботникиОрганизаций.СрезПоследних(&д2, ПодразделениеОрганизации = &Подр2
И ПричинаИзмененияСостояния = &ПричинаИзмененияСостояния) КАК Работники
ГДЕ
Работники.Период МЕЖДУ &д3 И &д2
Обратите внимание на использование ОБЪЕДИНИТЬ ВСЕ вместо ОБЪЕДИНИТЬ. Оператор ОБЪЕДИНИТЬ выполняет проверку на уникальность строк (аналог DISTINCT), что требует дополнительных ресурсов сервера. Если вы уверены, что наборы данных не пересекаются или дубликаты допустимы, всегда выбирайте ОБЪЕДИНИТЬ ВСЕ.
При работе с кадровыми данными в ЗУП 2.5 следует учитывать несколько критических моментов, которые могут повлиять на корректность отчета:
СрезПоследних на дату &д2, но его последнее состояние будет «Увольнение». В таком случае он не должен отображаться в списке активного персонала.Граница с видом ВидГраницы.Включая.СрезПоследних на дату &д2 с условием Период МЕЖДУ &д3 И &д2 покажет только тех сотрудников, чье последнее изменение в регистре произошло именно в этот период. Если сотрудника приняли, а потом через день перевели в другое подразделение (внутри одного периода), СрезПоследних покажет только запись о переводе.Если ваш отчет предполагает дальнейшее соединение с данными о должностях, окладах или контактной информации, лучше всего использовать Временные Таблицы. Рассмотрим, как это упрощает структуру:
Во-первых, мы создаем временную таблицу ВТ_Сотрудники, куда помещаем результат нашего объединения. Во-вторых, в основном запросе мы просто выбираем данные из этой таблицы и соединяем их с другими регистрами (например, ФИОФизЛиц или ДолжностиОрганизаций).
Это делает код читаемым, облегчает отладку в консоли запросов и позволяет системе 1С построить наиболее оптимальный план выполнения запроса на уровне SQL-сервера.