При работе с периодическими регистрами сведений в 1С разработчики часто сталкиваются с необходимостью получения среза последних данных на определенную дату. Обычно для этого используется стандартная виртуальная таблица СрезПоследних. Однако стандартные механизмы отлично работают только в простых случаях. Если перед нами стоит задача реализовать сложную логику «отката» (fallback) — например, когда при наличии определенного значения в самой свежей записи нам требуется проигнорировать её и «откатиться» к более ранней записи, отвечающей совсем другим критериям — одной виртуальной таблицей обойтись не удастся.
В этой статье мы подробно разберем, как решить эту задачу с помощью пакетного запроса и временных таблиц, а также выясним, почему стандартные условия в параметрах виртуальной таблицы в данном случае не работают.
Рассмотрим практическую ситуацию. У нас есть периодический регистр сведений Товары, в котором хранится история рейтингов товаров по организациям. Измерения регистра: Организация, Товар. Ресурсы/реквизиты: Шкала (международная или национальная), Рейтинг.
Нам необходимо построить отчет на определенную дату и вывести актуальный рейтинг по каждой организации, соблюдая следующие правила:
Посмотрим на пример данных для организации «Паркер»:
Для организации «Ракета» данные выглядят проще:
Попытка написать условие непосредственно в параметрах виртуальной таблицы СрезПоследних, используя конструкцию ВЫБОР КОГДА, обречена на неудачу по двум ключевым причинам:
Во-первых, синтаксическое ограничение. Второй параметр виртуальной таблицы СрезПоследних(Период, Условие) ожидает простое логическое выражение фильтрации данных (аналогично секции ГДЕ обычного запроса). Использование условных конструкций ВЫБОР КОГДА ... ТОГДА Поле = Значение внутри этого параметра синтаксически недопустимо в языке запросов 1С.
Во-вторых, логическое ограничение. Даже если мы напишем статическое условие фильтрации (например, отсечем записи с рейтингом «отказ»), виртуальная таблица СрезПоследних сначала применит этот фильтр к физической таблице, а затем выберет последние записи из оставшихся. В случае с организацией «Паркер» система просто проигнорирует запись от 16.05.2019 («отказ») и вернет предыдущую международную запись от 15.03.2019 со значением «Вв». Но наша бизнес-логика требует совершить качественный переход: если на последнюю дату зафиксирован отказ по международной шкале, мы обязаны взять запись по национальной шкале (то есть запись от 10.02.2019 со значением «Аа»). Такое ветвление алгоритма невозможно реализовать за один проход по виртуальной таблице среза.
Чтобы реализовать эту сложную логику, нам понадобится составить пакетный запрос. Разделим решение на несколько логических шагов:
Товары по организации, ограничим выборку записями, которые были раньше даты отказа, и отберем только шкалу «национальная». Сгруппировав результат, мы найдем максимальную (самую свежую из прошлых) дату для этой шкалы.Рассмотрим по шагам, как выглядит текст запроса для реализации нашей логики. Мы будем использовать временные таблицы для сохранения промежуточных результатов.
// ШАГ 1. Получаем срез последних записей по всем организациям без фильтров по шкале.
// Это необходимо, чтобы определить текущий актуальный статус организации.
ВЫБРАТЬ
ТоварыСрезПоследних.Организация КАК Организация,
ТоварыСрезПоследних.Период КАК ПериодПоследний,
ТоварыСрезПоследних.Шкала КАК ШкалаПоследняя,
ТоварыСрезПоследних.Рейтинг КАК РейтингПоследний,
ТоварыСрезПоследних.Товар КАК ТоварПоследний
ПОМЕСТИТЬ ВТ_ПоследниеЗаписи
ИЗ
РегистрСведений.Товары.СрезПоследних(&Дата, ) КАК ТоварыСрезПоследних
;
////////////////////////////////////////////////////////////////////////////////
// ШАГ 2. Выбираем организации, у которых последний статус — это "отказ" на международной шкале.
// Для них ищем дату последней записи в истории с национальной шкалой.
ВЫБРАТЬ
ВТ_ПоследниеЗаписи.Организация КАК Организация,
МАКСИМУМ(ТоварыФизическая.Период) КАК ПериодПредыдущий
ПОМЕСТИТЬ ВТ_ПредыдущиеПериоды
ИЗ
ВТ_ПоследниеЗаписи КАК ВТ_ПоследниеЗаписи
// Соединяемся с физической таблицей регистра для анализа истории изменений
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.Товары КАК ТоварыФизическая
ПО ВТ_ПоследниеЗаписи.Организация = ТоварыФизическая.Организация
// Берем только те записи, которые были раньше даты зафиксированного отказа
И ТоварыФизическая.Период < ВТ_ПоследниеЗаписи.ПериодПоследний
И ТоварыФизическая.Шкала = "национальная"
ГДЕ
ВТ_ПоследниеЗаписи.РейтингПоследний ПОДОБНО "%отказ%"
И ВТ_ПоследниеЗаписи.ШкалаПоследняя = "международная"
СГРУППИРОВАТЬ ПО
ВТ_ПоследниеЗаписи.Организация
;
////////////////////////////////////////////////////////////////////////////////
// ШАГ 3. Получаем полные данные (рейтинг, товар и т.д.) для найденных предыдущих периодов.
ВЫБРАТЬ
Товары.Организация КАК Организация,
Товары.Период КАК Период,
Товары.Шкала КАК Шкала,
Товары.Рейтинг КАК Рейтинг,
Товары.Товар КАК Товар
ПОМЕСТИТЬ ВТ_ПредыдущиеДанные
ИЗ
ВТ_ПредыдущиеПериоды КАК ВТ_ПредыдущиеПериоды
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.Товары КАК Товары
ПО ВТ_ПредыдущиеПериоды.Организация = Товары.Организация
И ВТ_ПредыдущиеПериоды.ПериодПредыдущий = Товары.Период
;
////////////////////////////////////////////////////////////////////////////////
// ШАГ 4. Объединяем результаты в единую выборку.
// Часть 1: берем записи без отказов по международной шкале.
ВЫБРАТЬ
ВТ_ПоследниеЗаписи.Организация КАК Организация,
ВТ_ПоследниеЗаписи.ПериодПоследний КАК Период,
ВТ_ПоследниеЗаписи.ШкалаПоследняя КАК Шкала,
ВТ_ПоследниеЗаписи.РейтингПоследний КАК Рейтинг,
ВТ_ПоследниеЗаписи.ТоварПоследний КАК Товар
ИЗ
ВТ_ПоследниеЗаписи КАК ВТ_ПоследниеЗаписи
ГДЕ
НЕ ВТ_ПоследниеЗаписи.РейтингПоследний ПОДОБНО "%отказ%"
И ВТ_ПоследниеЗаписи.ШкалаПоследняя = "международная"
ОБЪЕДИНИТЬ ВСЕ
// Часть 2: присоединяем восстановленные данные по национальной шкале для тех, у кого был отказ.
ВЫБРАТЬ
ВТ_ПредыдущиеДанные.Организация,
ВТ_ПредыдущиеДанные.Период,
ВТ_ПредыдущиеДанные.Шкала,
ВТ_ПредыдущиеДанные.Рейтинг,
ВТ_ПредыдущиеДанные.Товар
ИЗ
ВТ_ПредыдущиеДанные КАК ВТ_ПредыдущиеДанные
Проследим за логикой работы нашего запроса на примере двух организаций из условия задачи:
Организация «Ракета»:
ВТ_ПоследниеЗаписи попадает строка: 19.08.2019, шкала «международная», рейтинг «Пп».ГДЕ, так как рейтинг «Пп» не содержит подстроку «отказ». Соответственно, в таблицы ВТ_ПредыдущиеПериоды и ВТ_ПредыдущиеДанные записи по этой организации не попадут.ОБЪЕДИНИТЬ ВСЕ условие выполняется (рейтинг не «отказ», шкала «международная»), и запись успешно попадает в итоговый результат.Организация «Паркер»:
ВТ_ПоследниеЗаписи записывается строка: 16.05.2019, шкала «международная», рейтинг «отказ».ГДЕ. Запрос обращается к истории физической таблицы по этой организации, находит запись со шкалой «национальная» от 10.02.2019 (она меньше даты отказа 16.05.2019) и вычисляет её период как максимальный.ВТ_ПредыдущиеДанные.ВТ_ПредыдущиеДанные выбирается восстановленная запись от 10.02.2019.В результате работы запроса мы получаем ровно те две строки, которые требовались по условию бизнес-логики.
Если данный запрос разрабатывается исключительно в рамках Системы компоновки данных (СКД), можно рассмотреть альтернативный вариант связывания данных на уровне платформы. Это позволит избежать громоздких пакетных запросов, хотя может несколько уступать им в производительности на огромных базах данных.
Для реализации этого метода мы создаем два независимых набора данных-запроса в схеме компоновки:
ГДЕ мы прописываем параметры ограничения по периоду и организации: ГДЕ Организация = &ПараметрОрганизация И Период < &ПараметрПериодПредыдущий.Затем переходим на вкладку Связи наборов данных и настраиваем связь:
НаборДанных1НаборДанных2Организация = ОрганизацияПериодПоследний из первого набора в качестве параметра &ПараметрПериодПредыдущий во второй набор данных.В самом отчете с помощью условного оформления или вычисляемых полей мы сможем выводить либо данные первого набора (если нет отказа), либо данные второго набора (если наступило событие отката). Тем не менее, использование единого пакетного запроса, который мы подробно разобрали выше, является классическим, более производительным и легко отлаживаемым решением в среде 1С:Предприятия.