Как правильно получить срез последних по регистру сведений без дублей и логических ошибок?

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

Работа с виртуальными таблицами в 1С — это базовая задача для любого разработчика, однако именно в ней скрыто множество нюансов. Одной из самых коварных является таблица СрезПоследних. Часто возникают ситуации, когда запрос возвращает «странные» данные: записи дублируются, или в выборку попадают сотрудники, которые по логике вещей должны быть исключены. В этой статье мы подробно разберем, почему так происходит, и как составить запрос, который будет работать быстро и корректно.

Разберем причину появления дублей в запросе

Первая проблема, с которой часто сталкиваются разработчики, — это неожиданное появление нескольких строк по одному и тому же объекту (например, сотруднику), хотя ожидается только одна «последняя» запись. Рассмотрим ситуацию: мы делаем запрос к виртуальной таблице СрезПоследних, указываем сотрудника, но получаем две или три строки. Проанализируем, почему это происходит.

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

Решение проблемы дублей: чтобы понять, почему данные двоятся, попробуем временно добавить в запрос абсолютно все поля из регистра (все измерения и ресурсы). Это позволит увидеть, в каком именно поле значения различаются. Если нам нужен срез строго по сотруднику, а в регистре есть другие измерения, нам придется либо использовать группировку, либо накладывать дополнительные условия на эти измерения.

Ловушка параметров виртуальной таблицы: фильтрация по состоянию

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


ВЫБРАТЬ
    СостоянияСрез.Сотрудник,
    СостоянияСрез.Состояние
ИЗ
    РегистрСведений.СостоянияСотрудников.СрезПоследних(&ТекДата, Состояние <> &СостояниеУвольнение) КАК СостоянияСрез

Проанализируем, как этот запрос обрабатывается платформой и SQL-сервером. Когда мы указываем фильтр Состояние <> &СостояниеУвольнение в параметрах виртуальной таблицы, система выполняет следующие действия:

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

В чем здесь подвох? Представим историю сотрудника:
01.01.2023 — Принят (Работает)
01.12.2023 — Уволен.
Если мы выполним запрос с фильтром по состоянию в параметрах, система проигнорирует запись об увольнении и вернет нам запись от 01.01.2023 как «самую последнюю из подходящих». В итоге программа сообщит, что сотрудник работает, хотя на самом деле он уволен уже год назад. Это классическая логическая ошибка при работе со срезом последних.

Как правильно фильтровать срез последних

Чтобы избежать описанной выше проблемы, мы должны сначала получить «честный» срез последних (самые актуальные записи на дату), а уже затем анализировать, какое состояние оказалось в этой актуальной записи. Выясним, как это реализовать на практике.

Способ 1: Фильтрация в секции ГДЕ. Это самый простой способ исправить запрос. Мы убираем фильтр по ресурсам из параметров виртуальной таблицы и переносим его в основное условие запроса.


ВЫБРАТЬ
    СостоянияСрез.Сотрудник,
    СостоянияСрез.Состояние
ИЗ
    РегистрСведений.СостоянияСотрудников.SrezPoslednih(&ТекДата, ) КАК СостоянияСрез
ГДЕ
    СостоянияСрез.Состояние <> &СостояниеУвольнение

В этом случае система сначала найдет последнюю запись для каждого сотрудника (например, «Уволен»), а затем условие ГДЕ просто исключит этого сотрудника из итоговой выборки, так как его актуальное состояние не удовлетворяет условию.

Способ 2: Использование подзапроса для исключения. Если задача стоит жестко — получить список тех, кто точно не уволен, можно использовать конструкцию с подзапросом, которую применил автор темы в финальном сообщении. Хотя она может быть менее производительной на огромных массивах данных, она наглядно решает задачу:


ВЫБРАТЬ
    СостоянияСрез.Сотрудник,
    СостоянияСрез.Состояние
ИЗ
    РегистрСведений.СостоянияСотрудников.СрезПоследних(&ТекДата, ) КАК СостоянияСрез
ГДЕ
    НЕ СостоянияСрез.Сотрудник В 
    (
        ВЫБРАТЬ
            Т.Сотрудник
        ИЗ
            РегистрСведений.СостоянияСотрудников.СрезПоследних(&ТекДата, Состояние = &СостояниеУвольнение) КАК Т
    )

Оптимизация производительности: использование временных таблиц

Рассмотрим ситуацию, когда база данных содержит сотни тысяч записей. Прямое обращение к СрезПоследних с последующим соединением с другими таблицами (например, со справочником Сотрудники или ФизическиеЛица) может привести к серьезным задержкам. Это происходит потому, что 1С сначала пытается вычислить срез для всего регистра, что является очень тяжелой операцией.

Проанализируем профессиональный подход к решению этой задачи:

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

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


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

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

Нюансы учета: Сотрудники против Физических лиц

Разберем важный методологический момент, упомянутый в обсуждении. В конфигурациях вроде ЗУП (Зарплата и Управление Персоналом) один человек (Физическое лицо) может занимать несколько должностей (быть несколькими «Сотрудниками» — по основному месту и по совместительству).

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

Итоги: контрольный список при работе со срезом последних

Подводя итог нашему разбору, выделим правила, которые помогут избежать ошибок:

Соблюдение этих правил гарантирует корректность получения данных и высокую скорость работы ваших отчетов и обработок.

← На главную