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