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

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

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

Причина ограничения виртуальной таблицы СрезПоследних

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

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

Решение 1. Использование физической таблицы и нестрогого (тета) соединения

Рассмотрим основной и самый универсальный метод решения этой задачи на языке запросов 1С. Так как виртуальную таблицу использовать нельзя, обратимся напрямую к физической таблице регистра сведений.

Процесс решения состоит из трех основных шагов:

  1. Подготовка исходных данных и дат: формируем временную таблицу с базовыми данными (например, список сотрудников и интересующие даты — концы месяцев).
  2. Поиск наибольшей даты записи (тета-соединение): соединяем временную таблицу с физической таблицей регистра по условию ПериодИсточника >= ПериодРегистра и группировкой находим МАКСИМУМ(ПериодРегистра) для каждого месяца.
  3. Получение итоговых данных: связываем полученные максимальные даты обратно с физической таблицей регистра сведений для выборки нужных реквизитов (подразделения, должности и т.д.).

Разберем готовый пример запроса, демонстрирующий данный подход:


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

ИЗМЕНИТЬ ИНДЕКС ПО ВТ_Выплаты (Сотрудник, Период);

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

ИЗМЕНИТЬ ИНДЕКС ПО ВТ_ПериодыПодразделений (Сотрудник, ПериодПодразделения);

////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Соединяем с физической таблицей по найденной максимальной дате
ВЫБРАТЬ
	ВТ_Выплаты.Сотрудник КАК Сотрудник,
	ВТ_Выплаты.Период КАК Период,
	ВТ_Выплаты.Сумма КАК Сумма,
	РаботникиОрганизаций.Подразделение КАК Подразделение
ИЗ
	ВТ_Выплаты КАК ВТ_Выплаты
		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПериодыПодразделений КАК ВТ_ПериодыПодразделений
		ПО ВТ_Выплаты.Сотрудник = ВТ_ПериодыПодразделений.Сотрудник
			И ВТ_Выплаты.Период = ВТ_ПериодыПодразделений.Период
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.РаботникиОрганизаций КАК РаботникиОрганизаций
		ПО ВТ_ПериодыПодразделений.Сотрудник = РаботникиОрганизаций.Сотрудник
			И ВТ_ПериодыПодразделений.ПериодПодразделения = РаботникиОрганизаций.Период;

Важное замечание по производительности и дублям строк:

Если в регистре сведений по одному сотруднику может быть зафиксировано несколько записей на одну и ту же секунду (один период), простое соединение по периоду может привести к размножению строк. Для исключения дублей рекомендуется в группировку и связи добавлять поля МАКСИМУМ(Регистратор) (если регистр подчинен регистратору) и МАКСИМУМ(НомерСтроки).

Для оптимизации нагрузки на СУБД всегда используйте ограничение физической таблицы регистра по максимальной дате в секции ГДЕ (например, РаботникиОрганизаций.Период <= &ДатаКон) и индексируйте временные таблицы по полям связи.

Решение 2. Динамическое построение запроса в цикле

Проанализируем второй вариант. Если количество анализируемых периодов заранее известно и невелико (например, 12 месяцев одного года), можно сформировать текст запроса динамически в коде 1С, объединяя виртуальные таблицы среза последних для каждого месяца через ключевое слово ОБЪЕДИНИТЬ ВСЕ.

Рассмотрим пример кода на встроенном языке 1С:


ТекМесяц = НачалоГода(ДатаОтчета);
Запрос = Новый Запрос;

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

Запрос.Текст = ТекстЗапроса;
Результат = Запрос.Выполнить().Выгрузить();

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

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

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

  1. Набор данных 1 (Запрос): выбирает основные данные (список сотрудников, суммы выплаты, период/месяц).
  2. Набор данных 2 (Запрос): выбирает данные из виртуальной таблицы РегистрСведений.РаботникиОрганизаций.СрезПоследних(&ПериодСреза, Сотрудник = &Сотрудник).
  3. Настройки связей: на вкладке «Связи наборов данных» указываем связь от Набора 1 к Набору 2. В колонке «Выражение источник» указываем поле `Период` (или `Сотрудник`), а в колонке «Параметр» — соответствующий параметр виртуальной таблицы &ПериодСреза (или &Сотрудник).

При таком подходе СКД сама выполнит необходимые запросы к виртуальным таблицам на каждую дату выборки без потери производительности и без усложнения текста основных запросов.

← На главную