Как правильно связать физическое лицо и сотрудника в запросе 1С без дублирования строк

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

При разработке отчетов и обработок в конфигурациях 1С (особенно в 1С:Зарплата и управление персоналом, 1С:ERP или 1С:Комплексная автоматизация) программисты регулярно сталкиваются с классической задачей: для списка физических лиц необходимо получить данные по их сотрудникам. Основная сложность заключается в том, что связь между справочниками Справочник.ФизическиеЛица и Справочник.Сотрудники (или Справочник.СотрудникиОрганизаций в старых редакциях) имеет отношение "один ко многим".

У одного физического лица в информационной базе может быть создано несколько карточек сотрудников. Это происходит по разным причинам: внутреннее или внешнее совместительство, увольнение и повторный прием на работу, работа по договорам ГПХ (гражданско-правового характера) или работа в разных организациях одной компании. Если написать простой запрос со связью по полю Физлицо, в результате мы получим дублирование строк для тех граждан, у которых заведено несколько карточек сотрудников.

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

Почему МАКСИМУМ(Ссылка) — это опасный архитектурный антипаттерн

В ходе обсуждения на форумах часто предлагают «быстрое» решение: сгруппировать таблицу по полю Физлицо и применить агрегатную функцию МАКСИМУМ к полю Ссылка (Сотрудник). Посмотрим на пример такого ошибочного запроса:


ВЫБРАТЬ
    СотрудникиОрганизаций.Физлицо КАК Физлицо,
    МАКСИМУМ(СотрудникиОрганизаций.Ссылка) КАК Сотрудник
ПОМЕСТИТЬ ВТ_Сотрудники
ИЗ
    Справочник.СотрудникиОрганизаций КАК СотрудникиОрганизаций
СГРУППИРОВАТЬ ПО
    СотрудникиОрганизаций.Физлицо

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

  1. Сравнение по GUID: Платформа 1С:Предприятие при использовании функций МАКСИМУМ или МИНИМУМ для ссылочных типов данных производит сравнение их уникальных идентификаторов (UUID/GUID) на уровне СУБД (SQL Server, PostgreSQL и т.д.).
  2. Отсутствие логики в GUID: Уникальный идентификатор — это шестнадцатеричная строка, которая генерируется платформой автоматически при создании нового объекта. Она не имеет никакой прямой связи с датой создания элемента, его кодом, наименованием, актуальностью или важностью. «Максимальная» ссылка — это просто случайная запись с точки зрения бизнес-логики.
  3. Непредсказуемость результатов: Использование функции МАКСИМУМ(Ссылка) может вернуть уволенного сотрудника вместо действующего, совместителя вместо основного работника или архивный дубль. Поведение системы становится непредсказуемым. Более того, при переносе данных в другую базу (например, через конвертацию данных или механизм РИБ) идентификаторы могут создаваться в ином порядке, и один и тот же запрос на одинаковых данных выдаст разные результаты.

Таким образом, использование МАКСИМУМ(Ссылка) дает лишь иллюзию корректной работы, скрывая логическую ошибку в кодовой базе.

Почему фильтрации по архиву бывает недостаточно

Другая распространенная попытка решить проблему — добавить условие ВАрхиве = Ложь (или проверить, что сотрудник не уволен). На первый взгляд, это отсекает старые карточки. Выясним, почему этого тоже недостаточно:

Правильное решение №1: Использование системы весов (приоритетов) в запросе

Чтобы гарантированно и корректно выбрать ровно одного сотрудника для физического лица в рамках одного запроса, мы должны явно описать правила выбора приоритетного сотрудника. Разработаем алгоритм ранжирования на основе конструкции ВЫБОР.

Определим классические приоритеты для выбора сотрудника:

  1. Приоритет 1 (Самый высокий): Действующий сотрудник (не в архиве), работающий по основному месту работы.
  2. Приоритет 2: Действующий сотрудник (не в архиве), работающий по совместительству.
  3. Приоритет 3: Действующий исполнитель по договору ГПХ.
  4. Приоритет 4: Архивный (уволенный) сотрудник по основному месту работы.
  5. Приоритет 5 (Самый низкий): Любые прочие архивные записи.

Реализуем этот алгоритм с помощью пакета запросов во временных таблицах:


// Шаг 1: Рассчитываем числовой приоритет для каждого сотрудника
ВЫБРАТЬ
    Сотрудники.Физлицо КАК Физлицо,
    Сотрудники.Ссылка КАК Сотрудник,
    ВЫБОР
        КОГДА НЕ Сотрудники.ВАрхиве И Сотрудники.ВидЗанятости = ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.ОсновноеМестоРаботы) ТОГДА 1
        КОГДА НЕ Сотрудники.ВАрхиве И Сотрудники.ВидЗанятости = ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.Совместительство) ТОГДА 2
        КОГДА НЕ Сотрудники.ВАрхиве ТОГДА 3
        КОГДА Сотрудники.ВАрхиве И Сотрудники.ВидЗанятости = ЗНАЧЕНИЕ(Перечисление.ВидыЗанятости.ОсновноеМестоРаботы) ТОГДА 4
        ИНАЧЕ 5
    КОНЕЦ КАК Приоритет
ПОМЕСТИТЬ ВТ_СотрудникиСПриоритетом
ИЗ
    Справочник.Сотрудники КАК Сотрудники
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Находим минимальный (лучший) приоритет для каждого физического лица
ВЫБРАТЬ
    ВТ_СотрудникиСПриоритетом.Физлицо КАК Физлицо,
    МИНИМУМ(ВТ_СотрудникиСПриоритетом.Приоритет) КАК МинимальныйПриоритет
ПОМЕСТИТЬ ВТ_ЛучшийПриоритет
ИЗ
    ВТ_СотрудникиСПриоритетом КАК ВТ_СотрудникиСПриоритетом
СГРУППИРОВАТЬ ПО
    ВТ_СотрудникиСПриоритетом.Физлицо
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Соединяем таблицы обратно, чтобы получить единственную ссылку на сотрудника
ВЫБРАТЬ
    ВТ_ЛучшийПриоритет.Физлицо КАК Физлицо,
    МАКСИМУМ(ВТ_СотрудникиСПриоритетом.Сотрудник) КАК Сотрудник
ПОМЕСТИТЬ ВТ_ИтоговыеСотрудники
ИЗ
    ВТ_ЛучшийПриоритет КАК ВТ_ЛучшийПриоритет
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_СотрудникиСПриоритетом КАК ВТ_СотрудникиСПриоритетом
        ПО ВТ_ЛучшийПриоритет.Физлицо = ВТ_СотрудникиСПриоритетом.Физлицо
        И ВТ_ЛучшийПриоритет.МинимальныйПриоритет = ВТ_СотрудникиСПриоритетом.Приоритет
СГРУППИРОВАТЬ ПО
    ВТ_ЛучшийПриоритет.Физлицо

Важное примечание к Шагу 3: Обратите внимание, что на третьем шаге мы все равно используем функцию МАКСИМУМ(Сотрудник) с группировкой по физическому лицу. Однако здесь это абсолютно безопасно и корректно! Данная агрегатная функция сработает только в том крайнем случае, если у одного человека есть сразу два абсолютно одинаковых по приоритету сотрудника (например, два активных внутренних совместительства). В этой ситуации нам действительно подойдет любой из них, так как их бизнес-статус эквивалентен, а все неактуальные или менее приоритетные карточки мы уже гарантированно отсеяли на предыдущих этапах фильтрации.

Правильное решение №2: Использование регистра «Текущие кадровые данные сотрудников»

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

Использование этого регистра значительно упрощает задачу получения единственного сотрудника. Проанализируем пример запроса:


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

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

Правильное решение №3: Использование готовых программных методов БСП

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

Рассмотрим метод программного интерфейса кадрового учета:


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

Этот метод самостоятельно проанализирует все кадровые данные физического лица на указанную дату, учтет его состояние (в архиве или нет), вид занятости (основное место или совместительство) и вернет строго одну, наиболее корректную ссылку на сотрудника.

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


// Получение кадровых данных для массива физических лиц
МассивФизЛиц = Новый Массив;
МассивФизЛиц.Добавить(СсылкаНаФизЛицо);

ТаблицаДанных = КадровыйУчет.КадровыеДанныеФизическихЛиц(МассивФизЛиц, "Сотрудник, Подразделение, Должность", ТекущаяДата());

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

Подведем итоги

При решении задач сопоставления физических лиц и сотрудников всегда помните о разнице между этими сущностями. Избегайте соблазна использовать быстрые, но некорректные решения с агрегатными функциями над ссылками типа МАКСИМУМ(Ссылка). Проектируйте запросы с явным расчетом приоритетов кадрового состояния сотрудников, используйте специализированные регистры текущих кадровых данных или обращайтесь к готовым методам API 1С. Это обеспечит стабильность, предсказуемость и высокую скорость работы ваших прикладных решений.

← На главную