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

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

Часто при разработке отчетов в СКД возникает задача: получить данные из регистра сведений не на одну фиксированную дату, а на разные даты для каждой строки основного набора данных. Например, у нас есть список уволенных сотрудников и даты их увольнения, и для каждого нужно узнать количество ставок именно на его персональную дату увольнения.

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

Почему нельзя передать поле из ВТ в параметр виртуальной таблицы?

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


// Этот код НЕ РАБОТАЕТ
ВЫБРАТЬ
	КадровыеСведенияСрезПоследних.КоличествоСтавок
ИЗ
	РегистрСведений.КадровыеСведения.СрезПоследних(
		ВтДатаУвольнения.ДатаУвольнения, // <-- Проблема здесь
		Сотрудник В (ВЫБРАТЬ Сотрудник ИЗ ВтДатаУвольнения)
	) КАК КадровыеСведенияСрезПоследних

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

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

Решение 1: Формирование среза вручную с помощью соединений

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

Разберем процесс по шагам.

  1. Получаем исходные данные.

    Первым шагом, как и в оригинальном запросе, получаем список сотрудников и дат, на которые нужно получить срез. Поместим их во временную таблицу. Здесь же применим хорошую практику и заменим конструкцию ДОБАВИТЬКДАТЕ на стандартные функции НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА для корректной работы с границами дат.

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

    Теперь для каждой пары "Сотрудник - ДатаУвольнения" нам нужно найти последнюю по времени запись в регистре сведений, дата которой меньше или равна нашей дате увольнения. Для этого соединим нашу временную таблицу ВтДатыУвольнения с физической таблицей регистра РегистрСведений.КадровыеСведения. Ключевое условие соединения: КадровыеДанные.Период <= ВтДатыУвольнения.ДатаУвольнения. После этого сгруппируем результат и найдем максимальную дату (Период) для каждого сотрудника.

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

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

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

Объединив эти три части в один пакетный запрос, мы получим надежное и быстрое решение, полностью имитирующее желаемое поведение СрезПоследних для каждой строки.

Решение 2: Использование связей наборов данных в СКД

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

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

Разберем по шагам настройку в конструкторе СКД:

  1. Создайте "Набор данных 1: ДатыУвольнения".

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

    
    ВЫБРАТЬ
        КадровыеСведения.Сотрудник КАК Сотрудник,
        КадровыеСведения.ДатаУвольнения КАК ДатаУвольнения
    ИЗ
        РегистрСведений.КадровыеСведения КАК КадровыеСведения
    ГДЕ
        КадровыеСведения.ДатаУвольнения МЕЖДУ &ДатаНач И &ДатаКон
    
  2. Создайте "Набор данных 2: КадровыеСведения".

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

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

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

  3. Настройте связь.

    Перейдите на вкладку "Связи наборов данных".

    • Создайте новую связь.
    • Источник связи: ДатыУвольнения (наш первый набор).
    • Приемник связи: КадровыеСведения (наш второй набор).
    • В таблице "Выражения" настройте соответствие полей и параметров:
      • Выражение-источник: Сотрудник, Выражение-приемник: Сотрудник. Это свяжет наборы по сотруднику.
      • Выражение-источник: ДатаУвольнения, Выражение-приемник: ДатаСреза (снимите флажок "Поле"). Это передаст дату увольнения в параметр &ДатаСреза второго запроса.

Теперь при формировании отчета СКД для каждой строки из набора "ДатыУвольнения" выполнит запрос из набора "КадровыеСведения", подставив в него соответствующие значения сотрудника и даты увольнения. Этот метод очень удобен, но стоит помнить, что он может приводить к большому количеству отдельных запросов к базе данных, что на больших объемах данных может сказаться на производительности.

Итоги и рекомендации

Мы рассмотрели два рабочих подхода к решению задачи получения среза данных на разные даты для разных объектов.

Выбор метода зависит от конкретной задачи, но понимание обоих подходов позволит вам гибко и эффективно решать подобные проблемы в будущем.

← На главную