Часто при разработке отчетов в СКД возникает задача: получить данные из регистра сведений не на одну фиксированную дату, а на разные даты для каждой строки основного набора данных. Например, у нас есть список уволенных сотрудников и даты их увольнения, и для каждого нужно узнать количество ставок именно на его персональную дату увольнения.
Интуитивно хочется использовать виртуальную таблицу СрезПоследних, передав в нее в качестве параметра периода поле с датой из временной таблицы. Однако такой подход не сработает. Давайте разберемся, почему это происходит и какие существуют правильные способы решения этой задачи.
Проанализируем исходную проблему. Есть первый запрос, который собирает сотрудников и даты их увольнения во временную таблицу ВтДатаУвольнения. Второй запрос пытается использовать эту дату для получения среза:
// Этот код НЕ РАБОТАЕТ
ВЫБРАТЬ
КадровыеСведенияСрезПоследних.КоличествоСтавок
ИЗ
РегистрСведений.КадровыеСведения.СрезПоследних(
ВтДатаУвольнения.ДатаУвольнения, // <-- Проблема здесь
Сотрудник В (ВЫБРАТЬ Сотрудник ИЗ ВтДатаУвольнения)
) КАК КадровыеСведенияСрезПоследних
Причина ошибки кроется в механизме работы пакетных запросов и виртуальных таблиц в 1С. Параметры виртуальных таблиц (таких как СрезПоследних) должны быть вычислены на момент трансляции всего пакета запросов сервером 1С в SQL-код. В этот момент данные из временной таблицы ВтДатаУвольнения еще не существуют, они появятся только в процессе выполнения самого запроса. Система просто не знает, какое значение подставить. Поэтому передавать поле из одной временной таблицы в параметр виртуальной таблицы другой части запроса технически невозможно.
Теперь, когда мы выяснили причину, рассмотрим два эффективных способа обойти это ограничение.
Это наиболее правильный, универсальный и производительный метод. Вместо того чтобы полагаться на виртуальную таблицу, мы сами эмулируем ее логику с помощью соединений с физической таблицей регистра. Этот подход выполняется целиком на стороне СУБД и обычно работает очень быстро.
Разберем процесс по шагам.
Первым шагом, как и в оригинальном запросе, получаем список сотрудников и дат, на которые нужно получить срез. Поместим их во временную таблицу. Здесь же применим хорошую практику и заменим конструкцию ДОБАВИТЬКДАТЕ на стандартные функции НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА для корректной работы с границами дат.
// Шаг 1: Получаем список сотрудников и дат увольнения
ВЫБРАТЬ
КадровыеСведения.Сотрудник КАК Сотрудник,
КадровыеСведения.ДатаУвольнения КАК ДатаУвольнения
ПОМЕСТИТЬ ВтДатыУвольнения
ИЗ
РегистрСведений.КадровыеСведения КАК КадровыеСведения
ГДЕ
КадровыеСведения.ДатаУвольнения МЕЖДУ НАЧАЛОПЕРИОДА(&ДатаНач, ДЕНЬ) И КОНЕЦПЕРИОДА(&ДатаКон, ДЕНЬ)
И КадровыеСведения.ДатаУвольнения <> ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)
Теперь для каждой пары "Сотрудник - ДатаУвольнения" нам нужно найти последнюю по времени запись в регистре сведений, дата которой меньше или равна нашей дате увольнения. Для этого соединим нашу временную таблицу ВтДатыУвольнения с физической таблицей регистра РегистрСведений.КадровыеСведения. Ключевое условие соединения: КадровыеДанные.Период <= ВтДатыУвольнения.ДатаУвольнения. После этого сгруппируем результат и найдем максимальную дату (Период) для каждого сотрудника.
// Шаг 2: Для каждой даты увольнения находим последнюю дату записи в регистре
ВЫБРАТЬ
ВтДатыУвольнения.Сотрудник КАК Сотрудник,
ВтДатыУвольнения.ДатаУвольнения КАК ДатаУвольнения,
МАКСИМУМ(КадровыеДанные.Период) КАК ПоследняяДатаЗаписи
ПОМЕСТИТЬ ВтПоследниеДаты
ИЗ
ВтДатыУвольнения КАК ВтДатыУвольнения
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КадровыеСведения КАК КадровыеДанные
ПО ВтДатыУвольнения.Сотрудник = КадровыеДанные.Сотрудник
И ВтДатыУвольнения.ДатаУвольнения >= КадровыеДанные.Период
СГРУППИРОВАТЬ ПО
ВтДатыУвольнения.Сотрудник,
ВтДатыУвольнения.ДатаУвольнения
У нас есть таблица ВтПоследниеДаты, где для каждого уволенного сотрудника хранится точная дата последней кадровой записи. Осталось только соединиться с физической таблицей регистра еще раз по сотруднику и этой найденной дате, чтобы получить нужные нам ресурсы — в данном случае, КоличествоСтавок.
// Шаг 3: Получаем количество ставок на найденную последнюю дату
ВЫБРАТЬ
ВтПоследниеДаты.Сотрудник,
ВтПоследниеДаты.ДатаУвольнения,
ЕСТЬNULL(КадровыеДанные.КоличествоСтавок, 0) КАК СтавокНаДеньУвольнения
ИЗ
ВтПоследниеДаты КАК ВтПоследниеДаты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КадровыеСведения КАК КадровыеДанные
ПО ВтПоследниеДаты.Сотрудник = КадровыеДанные.Сотрудник
И ВтПоследниеДаты.ПоследняяДатаЗаписи = КадровыеДанные.Период
Объединив эти три части в один пакетный запрос, мы получим надежное и быстрое решение, полностью имитирующее желаемое поведение СрезПоследних для каждой строки.
Если задача решается исключительно в рамках отчета на СКД, есть другой, более простой в настройке, но потенциально менее производительный способ. Он заключается в использовании механизма связей наборов данных.
Суть метода: мы создаем два независимых набора данных. Первый получает список сотрудников и дат. Второй — это запрос к виртуальной таблице СрезПоследних, но с параметрами. Затем мы настраиваем связь, чтобы СКД для каждой строки первого набора сама выполняла второй, подставляя нужные значения в параметры.
Разберем по шагам настройку в конструкторе СКД:
На вкладке "Наборы данных" создайте новый набор данных типа "Запрос". Поместите в него первый запрос, получающий список сотрудников и дат их увольнения.
ВЫБРАТЬ
КадровыеСведения.Сотрудник КАК Сотрудник,
КадровыеСведения.ДатаУвольнения КАК ДатаУвольнения
ИЗ
РегистрСведений.КадровыеСведения КАК КадровыеСведения
ГДЕ
КадровыеСведения.ДатаУвольнения МЕЖДУ &ДатаНач И &ДатаКон
Создайте второй набор данных. Его запрос будет обращаться к виртуальной таблице СрезПоследних, но вместо конкретных значений в параметры будут подставлены параметры СКД, например, &ДатаСреза и &Сотрудник.
ВЫБРАТЬ
КадровыеСведенияСрезПоследних.КоличествоСтавок,
КадровыеСведенияСрезПоследних.Сотрудник
ИЗ
РегистрСведений.КадровыеСведения.СрезПоследних(&ДатаСреза, Сотрудник = &Сотрудник) КАК КадровыеСведенияСрезПоследних
Важно: убедитесь, что в списке полей этого набора данных есть поле, по которому будет осуществляться связь (Сотрудник).
Перейдите на вкладку "Связи наборов данных".
Сотрудник, Выражение-приемник: Сотрудник. Это свяжет наборы по сотруднику.ДатаУвольнения, Выражение-приемник: ДатаСреза (снимите флажок "Поле"). Это передаст дату увольнения в параметр &ДатаСреза второго запроса.Теперь при формировании отчета СКД для каждой строки из набора "ДатыУвольнения" выполнит запрос из набора "КадровыеСведения", подставив в него соответствующие значения сотрудника и даты увольнения. Этот метод очень удобен, но стоит помнить, что он может приводить к большому количеству отдельных запросов к базе данных, что на больших объемах данных может сказаться на производительности.
Мы рассмотрели два рабочих подхода к решению задачи получения среза данных на разные даты для разных объектов.
Выбор метода зависит от конкретной задачи, но понимание обоих подходов позволит вам гибко и эффективно решать подобные проблемы в будущем.