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