При разработке отчетов и обработок в конфигурациях 1С (особенно в 1С:Зарплата и управление персоналом, 1С:ERP или 1С:Комплексная автоматизация) программисты регулярно сталкиваются с классической задачей: для списка физических лиц необходимо получить данные по их сотрудникам. Основная сложность заключается в том, что связь между справочниками Справочник.ФизическиеЛица и Справочник.Сотрудники (или Справочник.СотрудникиОрганизаций в старых редакциях) имеет отношение "один ко многим".
У одного физического лица в информационной базе может быть создано несколько карточек сотрудников. Это происходит по разным причинам: внутреннее или внешнее совместительство, увольнение и повторный прием на работу, работа по договорам ГПХ (гражданско-правового характера) или работа в разных организациях одной компании. Если написать простой запрос со связью по полю Физлицо, в результате мы получим дублирование строк для тех граждан, у которых заведено несколько карточек сотрудников.
В этой статье мы подробно разберем, почему популярные простые решения этой проблемы являются грубыми ошибками, проанализируем архитектуру платформы 1С:Предприятие и рассмотрим правильные, надежные способы получения одного актуального сотрудника для каждого физического лица в запросах и программном коде.
В ходе обсуждения на форумах часто предлагают «быстрое» решение: сгруппировать таблицу по полю Физлицо и применить агрегатную функцию МАКСИМУМ к полю Ссылка (Сотрудник). Посмотрим на пример такого ошибочного запроса:
ВЫБРАТЬ
СотрудникиОрганизаций.Физлицо КАК Физлицо,
МАКСИМУМ(СотрудникиОрганизаций.Ссылка) КАК Сотрудник
ПОМЕСТИТЬ ВТ_Сотрудники
ИЗ
Справочник.СотрудникиОрганизаций КАК СотрудникиОрганизаций
СГРУППИРОВАТЬ ПО
СотрудникиОрганизаций.Физлицо
На первый взгляд, этот запрос работает: дубли исчезают, система возвращает строго одну строчку на каждое физическое лицо. Однако с технической точки зрения этот подход является грубой ошибкой. Разберем по шагам, почему так делать категорически нельзя:
МАКСИМУМ или МИНИМУМ для ссылочных типов данных производит сравнение их уникальных идентификаторов (UUID/GUID) на уровне СУБД (SQL Server, PostgreSQL и т.д.).МАКСИМУМ(Ссылка) может вернуть уволенного сотрудника вместо действующего, совместителя вместо основного работника или архивный дубль. Поведение системы становится непредсказуемым. Более того, при переносе данных в другую базу (например, через конвертацию данных или механизм РИБ) идентификаторы могут создаваться в ином порядке, и один и тот же запрос на одинаковых данных выдаст разные результаты.Таким образом, использование МАКСИМУМ(Ссылка) дает лишь иллюзию корректной работы, скрывая логическую ошибку в кодовой базе.
Другая распространенная попытка решить проблему — добавить условие ВАрхиве = Ложь (или проверить, что сотрудник не уволен). На первый взгляд, это отсекает старые карточки. Выясним, почему этого тоже недостаточно:
Чтобы гарантированно и корректно выбрать ровно одного сотрудника для физического лица в рамках одного запроса, мы должны явно описать правила выбора приоритетного сотрудника. Разработаем алгоритм ранжирования на основе конструкции ВЫБОР.
Определим классические приоритеты для выбора сотрудника:
Реализуем этот алгоритм с помощью пакета запросов во временных таблицах:
// Шаг 1: Рассчитываем числовой приоритет для каждого сотрудника
ВЫБРАТЬ
Сотрудники.Физлицо КАК Физлицо,
Сотрудники.Ссылка КАК Сотрудник,
ВЫБОР
КОГДА НЕ Сотрудники.ВАрхиве И Сотрудники.ВидЗанятости = ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.ОсновноеМестоРаботы) ТОГДА 1
КОГДА НЕ Сотрудники.ВАрхиве И Сотрудники.ВидЗанятости = ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.Совместительство) ТОГДА 2
КОГДА НЕ Сотрудники.ВАрхиве ТОГДА 3
КОГДА Сотрудники.ВАрхиве И Сотрудники.ВидЗанятости = ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.ОсновноеМестоРаботы) ТОГДА 4
ИНАЧЕ 5
КОНЕЦ КАК Приоритет
ПОМЕСТИТЬ ВТ_СотрудникиСПриоритетом
ИЗ
Справочник.Сотрудники КАК Сотрудники
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Находим минимальный (лучший) приоритет для каждого физического лица
ВЫБРАТЬ
ВТ_СотрудникиСПриоритетом.Физлицо КАК Физлицо,
МИНИМУМ(ВТ_СотрудникиСПриоритетом.Приоритет) КАК МинимальныйПриоритет
ПОМЕСТИТЬ ВТ_ЛучшийПриоритет
ИЗ
ВТ_СотрудникиСПриоритетом КАК ВТ_СотрудникиСПриоритетом
СГРУППИРОВАТЬ ПО
ВТ_СотрудникиСПриоритетом.Физлицо
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Соединяем таблицы обратно, чтобы получить единственную ссылку на сотрудника
ВЫБРАТЬ
ВТ_ЛучшийПриоритет.Физлицо КАК Физлицо,
МАКСИМУМ(ВТ_СотрудникиСПриоритетом.Сотрудник) КАК Сотрудник
ПОМЕСТИТЬ ВТ_ИтоговыеСотрудники
ИЗ
ВТ_ЛучшийПриоритет КАК ВТ_ЛучшийПриоритет
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_СотрудникиСПриоритетом КАК ВТ_СотрудникиСПриоритетом
ПО ВТ_ЛучшийПриоритет.Физлицо = ВТ_СотрудникиСПриоритетом.Физлицо
И ВТ_ЛучшийПриоритет.МинимальныйПриоритет = ВТ_СотрудникиСПриоритетом.Приоритет
СГРУППИРОВАТЬ ПО
ВТ_ЛучшийПриоритет.Физлицо
Важное примечание к Шагу 3: Обратите внимание, что на третьем шаге мы все равно используем функцию МАКСИМУМ(Сотрудник) с группировкой по физическому лицу. Однако здесь это абсолютно безопасно и корректно! Данная агрегатная функция сработает только в том крайнем случае, если у одного человека есть сразу два абсолютно одинаковых по приоритету сотрудника (например, два активных внутренних совместительства). В этой ситуации нам действительно подойдет любой из них, так как их бизнес-статус эквивалентен, а все неактуальные или менее приоритетные карточки мы уже гарантированно отсеяли на предыдущих этапах фильтрации.
В современных конфигурациях фирмы 1С (таких как ЗУП 3.1, ERP 2, КА 2) разработчики предусмотрели специальный вспомогательный регистр сведений — РегистрСведений.ТекущиеКадровыеДанныеСотрудников. Он спроектирован специально для быстрого получения актуальной кадровой информации без необходимости выполнять сложные запросы со множеством соединений к кадровым документам.
Использование этого регистра значительно упрощает задачу получения единственного сотрудника. Проанализируем пример запроса:
ВЫБРАТЬ
КадровыеДанные.ФизическоеЛицо КАК Физлицо,
КадровыеДанные.Сотрудник КАК Сотрудник
ПОМЕСТИТЬ ВТ_АктуальныеСотрудники
ИЗ
РегистрСведений.ТекущиеКадровыеДанныеСотрудников КАК КадровыеДанные
ГДЕ
НЕ КадровыеДанные.Сотрудник.ВАрхиве
И КадровыеДанные.ТекущаяОрганизация <> ЗНАЧЕНИЕ(Справочник.Организации.ПустаяСсылка)
Данный регистр хранит только актуальные связи. Проведя по нему простую группировку или установив отбор по основной организации, вы получите чистые данные без дублирования архивных записей.
Если перед вами стоит задача получить сотрудника по физическому лицу не в динамическом списке или сложном отчете СКД, а непосредственно в программном коде на встроенном языке 1С, вам не нужно самостоятельно писать запросы. Библиотека стандартных подсистем (БСП) и специализированные кадровые подсистемы современных конфигураций содержат готовые оптимизированные методы.
Рассмотрим метод программного интерфейса кадрового учета:
// Получение основного сотрудника для физического лица на определенную дату
СотрудникСсылка = КадровыйУчетРасширенный.ОсновнойСотрудникФизЛица(ВыбФизлицо, ВыбОрганизация, ТекущаяДата());
Этот метод самостоятельно проанализирует все кадровые данные физического лица на указанную дату, учтет его состояние (в архиве или нет), вид занятости (основное место или совместительство) и вернет строго одну, наиболее корректную ссылку на сотрудника.
Если вам требуется получить данные пакетно для списка физических лиц, используйте следующий метод:
// Получение кадровых данных для массива физических лиц
МассивФизЛиц = Новый Массив;
МассивФизЛиц.Добавить(СсылкаНаФизЛицо);
ТаблицаДанных = КадровыйУчет.КадровыеДанныеФизическихЛиц(МассивФизЛиц, "Сотрудник, Подразделение, Должность", ТекущаяДата());
Использование стандартных функций БСП гарантирует, что ваш код не сломается при обновлении конфигурации, если фирма 1С решит изменить структуру хранения кадровых данных в регистрах или справочниках.
При решении задач сопоставления физических лиц и сотрудников всегда помните о разнице между этими сущностями. Избегайте соблазна использовать быстрые, но некорректные решения с агрегатными функциями над ссылками типа МАКСИМУМ(Ссылка). Проектируйте запросы с явным расчетом приоритетов кадрового состояния сотрудников, используйте специализированные регистры текущих кадровых данных или обращайтесь к готовым методам API 1С. Это обеспечит стабильность, предсказуемость и высокую скорость работы ваших прикладных решений.