Как в запросе 1С получить предыдущую запись из регистра сведений, если на последней записи стоит отказ?

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

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

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

Анализ исходной задачи и почему не работает простой СрезПоследних

Рассмотрим практическую ситуацию. У нас есть периодический регистр сведений Товары, в котором хранится история рейтингов товаров по организациям. Измерения регистра: Организация, Товар. Ресурсы/реквизиты: Шкала (международная или национальная), Рейтинг.

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

  1. По умолчанию мы должны получить самую последнюю запись по организации со шкалой «международная», при условии, что рейтинг в ней не равен значению «отказ».
  2. Если же в самой последней записи организации по международной шкале зафиксирован рейтинг «отказ», мы должны найти предыдущую (более раннюю по времени) запись для этой организации, но уже со шкалой «национальная».

Посмотрим на пример данных для организации «Паркер»:

Для организации «Ракета» данные выглядят проще:

Попытка написать условие непосредственно в параметрах виртуальной таблицы СрезПоследних, используя конструкцию ВЫБОР КОГДА, обречена на неудачу по двум ключевым причинам:

Во-первых, синтаксическое ограничение. Второй параметр виртуальной таблицы СрезПоследних(Период, Условие) ожидает простое логическое выражение фильтрации данных (аналогично секции ГДЕ обычного запроса). Использование условных конструкций ВЫБОР КОГДА ... ТОГДА Поле = Значение внутри этого параметра синтаксически недопустимо в языке запросов 1С.

Во-вторых, логическое ограничение. Даже если мы напишем статическое условие фильтрации (например, отсечем записи с рейтингом «отказ»), виртуальная таблица СрезПоследних сначала применит этот фильтр к физической таблице, а затем выберет последние записи из оставшихся. В случае с организацией «Паркер» система просто проигнорирует запись от 16.05.2019 («отказ») и вернет предыдущую международную запись от 15.03.2019 со значением «Вв». Но наша бизнес-логика требует совершить качественный переход: если на последнюю дату зафиксирован отказ по международной шкале, мы обязаны взять запись по национальной шкале (то есть запись от 10.02.2019 со значением «Аа»). Такое ветвление алгоритма невозможно реализовать за один проход по виртуальной таблице среза.

Проектирование алгоритма решения

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

  1. Шаг 1. Сначала получим актуальный срез последних записей по всем организациям на заданную дату без каких-либо предварительных фильтров по рейтингу или шкале. Мы поместим эту информацию во временную таблицу. Это позволит нам зафиксировать текущее реальное состояние по каждой организации.
  2. Шаг 2. Проанализируем полученную таблицу. Для тех организаций, у которых последний рейтинг оказался «отказом» на международной шкале, нам нужно заглянуть в историю. Для этого мы соединим временную таблицу с физической таблицей регистра Товары по организации, ограничим выборку записями, которые были раньше даты отказа, и отберем только шкалу «национальная». Сгруппировав результат, мы найдем максимальную (самую свежую из прошлых) дату для этой шкалы.
  3. Шаг 3. По найденным датам получим полные данные предыдущих национальных записей из физической таблицы регистра.
  4. Шаг 4. Объединим два результирующих потока данных: «здоровые» последние записи по международной шкале (где нет отказа) и вычисленные «предыдущие» записи по национальной шкале для тех организаций, у которых был зафиксирован отказ.

Реализация пакетного запроса

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


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

////////////////////////////////////////////////////////////////////////////////
// ШАГ 2. Выбираем организации, у которых последний статус — это "отказ" на международной шкале.
// Для них ищем дату последней записи в истории с национальной шкалой.
ВЫБРАТЬ
	ВТ_ПоследниеЗаписи.Организация КАК Организация,
	МАКСИМУМ(ТоварыФизическая.Период) КАК ПериодПредыдущий
ПОМЕСТИТЬ ВТ_ПредыдущиеПериоды
ИЗ
	ВТ_ПоследниеЗаписи КАК ВТ_ПоследниеЗаписи
	// Соединяемся с физической таблицей регистра для анализа истории изменений
	ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.Товары КАК ТоварыФизическая
		ПО ВТ_ПоследниеЗаписи.Организация = ТоварыФизическая.Организация
		// Берем только те записи, которые были раньше даты зафиксированного отказа
		И ТоварыФизическая.Период < ВТ_ПоследниеЗаписи.ПериодПоследний
		И ТоварыФизическая.Шкала = "национальная"
ГДЕ
	ВТ_ПоследниеЗаписи.РейтингПоследний ПОДОБНО "%отказ%"
	И ВТ_ПоследниеЗаписи.ШкалаПоследняя = "международная"
СГРУППИРОВАТЬ ПО
	ВТ_ПоследниеЗаписи.Организация
;

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

////////////////////////////////////////////////////////////////////////////////
// ШАГ 4. Объединяем результаты в единую выборку.
// Часть 1: берем записи без отказов по международной шкале.
ВЫБРАТЬ
	ВТ_ПоследниеЗаписи.Организация КАК Организация,
	ВТ_ПоследниеЗаписи.ПериодПоследний КАК Период,
	ВТ_ПоследниеЗаписи.ШкалаПоследняя КАК Шкала,
	ВТ_ПоследниеЗаписи.РейтингПоследний КАК Рейтинг,
	ВТ_ПоследниеЗаписи.ТоварПоследний КАК Товар
ИЗ
	ВТ_ПоследниеЗаписи КАК ВТ_ПоследниеЗаписи
ГДЕ
	НЕ ВТ_ПоследниеЗаписи.РейтингПоследний ПОДОБНО "%отказ%"
	И ВТ_ПоследниеЗаписи.ШкалаПоследняя = "международная"

ОБЪЕДИНИТЬ ВСЕ

// Часть 2: присоединяем восстановленные данные по национальной шкале для тех, у кого был отказ.
ВЫБРАТЬ
	ВТ_ПредыдущиеДанные.Организация,
	ВТ_ПредыдущиеДанные.Период,
	ВТ_ПредыдущиеДанные.Шкала,
	ВТ_ПредыдущиеДанные.Рейтинг,
	ВТ_ПредыдущиеДанные.Товар
ИЗ
	ВТ_ПредыдущиеДанные КАК ВТ_ПредыдущиеДанные

Как этот алгоритм отрабатывает на тестовых данных

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

Организация «Ракета»:

Организация «Паркер»:

В результате работы запроса мы получаем ровно те две строки, которые требовались по условию бизнес-логики.

Реализация альтернативного варианта через механизмы СКД

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

Для реализации этого метода мы создаем два независимых набора данных-запроса в схеме компоновки:

Затем переходим на вкладку Связи наборов данных и настраиваем связь:

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

← На главную