Почему ЛЕВОЕ СОЕДИНЕНИЕ с условием в ГДЕ ведет себя как ВНУТРЕННЕЕ СОЕДИНЕНИЕ и как получить список работающих сотрудников в 1С:ЗУП?

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

При разработке запросов в системе 1С, особенно при работе со сложными структурами данных, мы часто сталкиваемся с необходимостью объединения информации из нескольких источников. Одним из таких инструментов является оператор ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN). Однако его использование требует глубокого понимания логики работы, особенно когда речь идет о наложении условий на правую часть соединения в секции ГДЕ (WHERE). Разберем ситуацию, которая часто вызывает затруднения у разработчиков, и рассмотрим, как правильно получать данные о работающих сотрудниках в конфигурации 1С:Зарплата и Управление Персоналом (ЗУП).

Суть проблемы: Неожиданное поведение ЛЕВОГО СОЕДИНЕНИЯ

Предположим, мы хотим получить список всех сотрудников из справочника и при этом дополнить их сведениями из регистра сведений «КадроваяИсторияСотрудниковСрезПоследних». Нам нужны только те сотрудники, которые не уволены. Исходный запрос, вызвавший вопрос, выглядел следующим образом:


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

Автор запроса ожидал, что будут выбраны все сотрудники из справочника Справочник.Сотрудники, а сведения о кадровой истории будут присоединены, если они есть. Если кадровой истории нет или событие — увольнение, то соответствующие поля из правой таблицы должны быть NULL или отсекаться. Однако на практике запрос возвращал только тех сотрудников, которые работали на указанную дату в выбранной организации и не имели события «Увольнение».

Почему же так происходит?

Разбираемся с логикой ЛЕВОГО СОЕДИНЕНИЯ и условиями в ГДЕ

  1. Принцип работы ЛЕВОГО СОЕДИНЕНИЯ: Оператор ЛЕВОЕ СОЕДИНЕНИЕ предназначен для включения всех записей из "левой" таблицы (в нашем случае, Справочник.Сотрудники), независимо от того, найдены ли для них соответствующие записи в "правой" таблице (в нашем случае, КадроваяИсторияСотрудниковСрезПоследних). Если для записи из левой таблицы нет совпадений в правой, поля из правой таблицы заполняются значением NULL.

  2. Влияние условия в секции ГДЕ: Секция ГДЕ применяется после выполнения соединения. Это означает, что после того, как ЛЕВОЕ СОЕДИНЕНИЕ уже сформировало промежуточную таблицу со всеми сотрудниками (и с NULL в правой части для тех, у кого нет кадровой истории или она не соответствует условиям ПО), условие КадроваяИсторияСотрудниковСрезПоследних.ВидСобытия <> ЗНАЧЕНИЕ(Перечисление.ВидыКадровыхСобытий.Увольнение) начинает фильтровать эти записи.

  3. Отсечение NULL-значений: Ключевой момент здесь заключается в том, что любое сравнение с NULL (за исключением специальных операторов типа ЕСТЬ NULL или НЕ ЕСТЬ NULL) всегда дает результат ЛОЖЬ или НЕИЗВЕСТНО. Соответственно, когда для сотрудника нет записи в регистре КадроваяИсторияСотрудниковСрезПоследних (или она была отфильтрована условиями в ПО), поле КадроваяИсторияСотрудниковСрезПоследних.ВидСобытия будет содержать NULL. Условие NULL <> ЗНАЧЕНИЕ(Перечисление.ВидыКадровыхСобытий.Увольнение) даст ЛОЖЬ, и такие строки будут отброшены.

  4. Превращение в ВНУТРЕННЕЕ СОЕДИНЕНИЕ: В результате, записи из левой таблицы, для которых не было совпадений в правой, исчезают из результата, поскольку их поля из правой таблицы были NULL и не прошли фильтр в ГДЕ. Таким образом, ЛЕВОЕ СОЕДИНЕНИЕ фактически ведет себя как ВНУТРЕННЕЕ СОЕДИНЕНИЕ, возвращая только те строки, для которых есть совпадения в обеих таблицах и которые удовлетворяют условию в ГДЕ.

Корректное решение для ЛЕВОГО СОЕДИНЕНИЯ

Чтобы сохранить логику ЛЕВОГО СОЕДИНЕНИЯ и при этом применить условие к правой таблице, необходимо перенести условие фильтрации из секции ГДЕ в секцию ПО оператора соединения. Это гарантирует, что условие будет применяться до формирования промежуточной таблицы, и для тех записей из левой таблицы, которые не удовлетворяют условию в правой, все равно будут созданы строки с NULL значениями, а не отброшены полностью.

Вот как будет выглядеть исправленный запрос:


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

В этом варианте, если для сотрудника найдется запись в регистре с видом события "Увольнение", или если записи вообще нет, то КадроваяИсторияСотрудниковСрезПоследних в строке результата будет иметь значения NULL, но строка для самого сотрудника из Справочник.Сотрудники при этом не будет отброшена. Таким образом, мы получаем всех сотрудников, а для работающих присоединяем актуальную кадровую историю (не увольнение).

Особенности получения работающих сотрудников в 1С:ЗУП 3.1

Хотя вышеописанные принципы работы SQL-запросов универсальны, в контексте конфигурации 1С:ЗУП 3.1 существуют свои нюансы, которые делают прямые запросы к регистрам менее надежными и эффективными. Рассмотрим почему и какие есть альтернативы:

  1. Сложность структуры кадровых данных в ЗУП: В ЗУП 3.1 кадровая история хранится не в одном, а в нескольких регистрах сведений (например, КадроваяИсторияСотрудников, РаботникиОрганизаций, ДанныеСостоянийСотрудников и другие). Кроме того, система активно использует механизмы исправлений и корректировок, что может усложнять получение актуальных данных через простые запросы "в лоб" к срезам последних. Прямой запрос к одному регистру может не учесть все нюансы состояния сотрудника.

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

  3. Регистры с "Периодом завершения": Некоторые периодические регистры в ЗУП используют "период завершения" для указания окончания действия записи. Это еще больше усложняет написание универсальных запросов без специальных функций.

Рекомендуемые подходы для получения списка работающих сотрудников в 1С:ЗУП

Для надежного и корректного получения списка работающих сотрудников на определенную дату в 1С:ЗУП 3.1 настоятельно рекомендуем использовать следующие методы:

1. Использование типовых функций общего модуля "КадровыйУчет"

Это наиболее предпочтительный программный способ. В конфигурации ЗУП предусмотрены специальные функции, инкапсулирующие сложную логику получения актуальных кадровых данных. Они учитывают всю специфику регистров, исправлений, корректировок и гарантируют правильный результат. Одной из таких функций является КадровыйУчет.СотрудникиОрганизации().

Рассмотрим пример её использования:


// Пример получения таблицы работающих сотрудников на определенную дату
ПараметрыПолучения = Новый Структура();
ПараметрыПолучения.Вставить("Дата", ДатаПерехода); // Дата, на которую нужны сотрудники
ПараметрыПолучения.Вставить("Организация", Организация); // Организация, если требуется фильтрация

ТаблицаСотрудников = КадровыйУчет.СотрудникиОрганизации(Истина, ПараметрыПолучения);
// Первый параметр 'Истина' означает, что будут получены только работающие сотрудники
// Результатом будет ТаблицаЗначений, содержащая необходимые данные о сотрудниках.

// Мы можем затем использовать эту ТаблицуСотрудников в дальнейших запросах,
// если потребуется дополнительная обработка или соединение с другими данными.

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

2. Использование Менеджера Временных Таблиц (МВТ)

Если мы получаем данные с помощью типовой функции КадровыйУчет.СотрудникиОрганизации() и нам нужно объединить эти данные с информацией из других источников с помощью запроса, мы можем использовать МенеджерВременныхТаблиц.

  1. Сначала мы получаем таблицу значений с помощью функции КадровыйУчет.СотрудникиОрганизации().

  2. Затем мы создаем временную таблицу в МенеджереВременныхТаблиц и загружаем в неё полученную таблицу значений.

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


// Пример использования Менеджера Временных Таблиц
МВТ = Новый МенеджерВременныхТаблиц;

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

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

Запрос.УстановитьПараметр("ТаблицаРаботающихСотрудников", ТаблицаРаботающихСотрудников);
Запрос.Выполнить(); // ВТ_РаботающиеСотрудники теперь содержит данные

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

Результат = Запрос2.Выполнить().Выгрузить();

Использование МВТ позволяет нам комбинировать данные, полученные различными способами, в рамках одного запроса, сохраняя при этом гибкость и производительность.

3. Механизм представлений в ЗУП для отчетов

Если задача состоит в построении отчета, то в ЗУП 3.1 активно используется так называемый "механизм представлений". Это особый подход к формированию источников данных для отчетов, который абстрагирует разработчика от сложной внутренней структуры конфигурации. В схеме компоновки данных (СКД) мы можем использовать "виртуальные" таблицы или представления, которые на самом деле не являются физическими таблицами или регистрами, а программно заполняются на стороне 1С, агрегируя данные из множества источников.

Мы описываем поля запроса как пустые ссылки, числа или даты, а фактическое заполнение происходит программно через специальные процедуры, такие как ЗаполнитьОбщиеИсточникиДанныхОтчета. Этот механизм обеспечивает высокую гибкость и устойчивость отчетов к изменениям внутренней структуры конфигурации, что является крайне важным для долгосрочной поддержки.

4. Стандартные отчеты 1С:ЗУП

Самым простым и быстрым способом для пользователя получить список работающих сотрудников является использование типовых отчетов конфигурации. Например, отчет "Штатные сотрудники" (раздел "Кадры" – "Кадровые отчеты") позволяет на определенную дату получить полный список сотрудников с различными кадровыми данными, включая признак работы, подразделение, должность и т.д. В этом отчете мы можем гибко настроить отборы по организации и дате, а также состав отображаемых полей.

Заключение

Мы выяснили, что кажущееся странным поведение ЛЕВОГО СОЕДИНЕНИЯ с условием в ГДЕ – это ожидаемая особенность его работы, приводящая к эффекту ВНУТРЕННЕГО СОЕДИНЕНИЯ. Для корректного сохранения логики ЛЕВОГО СОЕДИНЕНИЯ условия на правую часть следует переносить в секцию ПО.

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

← На главную