Почему не работает условие на пустую дату в параметрах виртуальной таблицы СрезПоследних?

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

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

В этой статье мы подробно разберем, почему так происходит, заглянем во внутренние механизмы работы платформы «1С» и СУБД, а также рассмотрим несколько правильных и оптимизированных способов решения этой архитектурной задачи.

Проанализируем типичную ситуацию. Нам необходимо выгрузить список работающих сотрудников на определенную дату. Признаком того, что сотрудник уволен, является заполненная колонка ДатаУвольнения. Соответственно, у работающих сотрудников это поле должно содержать пустую дату — 01.01.0001 (или ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0) в терминах языка запросов 1С).

В чем суть проблемы? Поведение «неправильного» запроса

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


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

Запустив этот запрос, мы с удивлением обнаружим, что в результат попали абсолютно все сотрудники, включая уволенных несколько лет назад. При этом, если мы выведем поле ДатаУвольнения, оно у всех уволенных сотрудников окажется пустым, хотя в самой базе данных там совершенно точно стоят реальные даты увольнения!

Но стоит нам перенести это же самое условие из параметров виртуальной таблицы в секцию ГДЕ, как запрос начинает работать абсолютно корректно:


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

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

Выясняем техническую причину: как СУБД строит СрезПоследних

Для того чтобы понять механику происходящего, нам нужно разграничить понятия измерений и ресурсов/реквизитов регистра сведений. В нашем примере Сотрудник и Фирма — это измерения (они определяют уникальность записи в регистре), а ДатаУвольнения — это ресурс или реквизит.

Когда платформа 1С транслирует наш запрос на язык SQL для СУБД, параметры виртуальной таблицы и условия в секции ГДЕ обрабатываются принципиально по-разному.

Разберем по шагам, что происходит, когда условие на ресурс накладывается в параметрах виртуальной таблицы:

  1. Платформа формирует SQL-запрос, в котором условия из параметров виртуальной таблицы накладываются непосредственно на физическую таблицу регистра в базе данных до расчета среза последних.
  2. В этот момент СУБД берет физическую таблицу и жестко отсекает из нее все строки, которые не соответствуют условию. То есть все строки, где ДатаУвольнения заполнена реальной датой увольнения (например, 15.05.2023), просто выбрасываются из выборки.
  3. Далее для каждого сотрудника СУБД пытается найти самую последнюю по периоду запись из тех, что остались в выборке.
  4. Поскольку запись об увольнении была отброшена на шаге 2, самой «последней» записью для уволенного сотрудника становится его предыдущая кадровая запись — например, запись о приеме на работу или кадровом перемещении, где ДатаУвольнения как раз была пустой!
  5. СУБД успешно находит эту «последнюю из оставшихся» запись и возвращает ее нам. В результате уволенный сотрудник отображается в отчете как работающий, а дата его увольнения выглядит пустой. Произошел так называемый «откат истории» назад во времени.

Теперь рассмотрим, что происходит, когда условие накладывается в секции ГДЕ:

  1. СУБД сначала рассчитывает честный срез последних по всей физической таблице без каких-либо предварительных фильтров по ресурсам. Для уволенного сотрудника самой последней записью будет именно документ увольнения, где ДатаУвольнения заполнена актуальной датой.
  2. После того как срез последних сформирован, на полученный результат накладывается фильтр ГДЕ.
  3. Система проверяет: равна ли дата увольнения в этой (последней) записи пустой дате? Нет, не равна, она заполнена. Проверка условия выдает Ложь, и запись этого сотрудника успешно и полностью удаляется из итогового результата запроса.

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

Влияние на производительность и Итоги регистров

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

В современных версиях платформы «1С:Предприятие 8.3» для периодических регистров сведений активно используется механизм «Разрешить итоги: Срез последних». СУБД хранит готовую таблицу последних записей, что позволяет получать данные среза мгновенно, минуя сканирование всей истории изменений.

Однако этот оптимизационный механизм работает исключительно тогда, когда все отборы в параметрах виртуальной таблицы наложены на измерения регистра. Если вы передаете туда условие по ресурсу или реквизиту, платформа не может задействовать готовую таблицу итогов. Ей приходится динамически выполнять ресурсоемкие подзапросы со сканированием всей физической таблицы регистра, что в разы увеличивает время выполнения запроса и нагрузку на сервер СУБД.

Как правильно решать эту задачу? Разберем 3 рабочих сценария

Как же поступить, если нам нужно получить список только работающих сотрудников, сделав это максимально быстро и правильно с точки зрения архитектуры?

Вариант 1. Классический отбор в секции ГДЕ

Если объем данных в регистре относительно небольшой, самым простым и надежным решением будет использование секции ГДЕ. Это гарантирует правильность результата.


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

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

Если регистр содержит сотни тысяч записей, а работающих сотрудников в текущий момент времени относительно мало, выполнение среза последних по всем сотрудникам с последующей фильтрацией в ГДЕ может перегрузить СУБД.

В этом случае мы можем разбить запрос на два этапа с помощью временных таблиц:

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

Посмотрим на пример реализации такого пакета запросов:


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

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

Этот подход позволяет СУБД максимально эффективно использовать индексы и не рассчитывать тяжелые срезы по тем полям, по которым в дальнейшем будет происходить фильтрация.

Вариант 3. Архитектурное изменение структуры метаданных

Если бизнес-логика вашего приложения требует регулярного и быстрого получения только работающих сотрудников, хранение признака увольнения в ресурсе периодического регистра является не совсем удачным решением.

Рассмотрим два варианта улучшения структуры метаданных:

Резюме

Подведем краткий итог нашего разбора:

← На главную