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

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

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

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

Метод 1. Использование физических таблиц регистра (Классический способ)

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

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

Проанализируем пример кода, который решает эту задачу через временные таблицы:


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

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

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

Важный нюанс: В этом примере мы используем ЛЕВОЕ СОЕДИНЕНИЕ. Это критически важно, так как если для какой-то номенклатуры в регистре вообще нет записей на указанную дату, мы должны сохранить строку документа в результате, получив NULL (который мы обрабатываем через ЕСТЬNULL).

Метод 2. Связи наборов данных в СКД

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

Разберем, как это настроить по шагам:

  1. Создадим Набор данных 1 (запрос), который выбирает список документов и их даты.
  2. Создадим Набор данных 2 (запрос), в котором обратимся к виртуальной таблице РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&ПериодСреза, Номенклатура = &ПараметрНоменклатура).
  3. Перейдем на закладку "Связи наборов данных".
  4. Укажем в качестве источника связи Набор данных 1, а в качестве приемника — Набор данных 2.
  5. В колонке "Условие связи" сопоставим Номенклатура (Источник) и ПараметрНоменклатура (Параметр приемника).
  6. Главное: Сопоставим поле ДатаДокумента из Источника с параметром ПериодСреза из Приемника.

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

Метод 3. Использование оконных функций (Платформа 8.3.20+)

С выходом версии платформы 8.3.20 у разработчиков появился мощный инструмент — оконные функции. Они позволяют решить задачу получения среза последних значительно быстрее и лаконичнее.

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

Пример запроса с оконной функцией:


ВЫБРАТЬ
    ДанныеДокументов.Номенклатура,
    ДанныеДокументов.ДатаДок,
    Цены.Цена,
    ПОРЯДКОВЫЙ_НОМЕР() ОБРАБОТКА ИЗ (ПАРТИЦИОНИРОВАТЬ ПО ДанныеДокументов.Номенклатура, ДанныеДокументов.ДатаДок УПОРЯДОЧИТЬ ПО Цены.Период УБЫВ) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_ПронумерованныеЦены
ИЗ
    ВТ_Данные КАК ДанныеДокументов
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
        ПО ДанныеДокументов.Номенклатура = Цены.Номенклатура
        И ДанныеДокументов.ДатаДок >= Цены.Период;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_ПронумерованныеЦены.Номенклатура,
    ВТ_ПронумерованныеЦены.ДатаДок,
    ВТ_ПронумерованныеЦены.Цена
ИЗ
    ВТ_ПронумерованныеЦены КАК ВТ_ПронумерованныеЦены
ГДЕ
    ВТ_ПронумерованныеЦены.НомерСтроки = 1;

Этот способ эффективнее классического соединения с МАКСИМУМ(Период), так как СУБД выполняет меньше операций сканирования таблиц, особенно на больших объемах данных.

Тонкие моменты и рекомендации

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

  1. Индексация: Для быстрого поиска по физической таблице (Метод 1) убедитесь, что в регистре сведений поле Период проиндексировано. В периодических регистрах это происходит автоматически, но если вы используете дополнительные измерения (например, ВидЦены), они также должны участвовать в условиях соединения и быть проиндексированы.
  2. Момент времени: Если документ и запись в регистре сведений могут иметь одинаковое время (вплоть до секунды), использование просто ДатаДокумента может привести к ошибкам (например, цена может "не успеть" примениться или, наоборот, взяться лишняя). В идеале для получения среза "на момент документа" в программном коде следует использовать объект Граница с включением момента времени документа.
  3. Производительность в СКД: Метод со связями в СКД удобен, но помните, что он выполняет запросы "в цикле" на уровне СУБД (или пакетами). Если в вашем списке десятки тысяч документов, это может работать медленнее, чем один сложный запрос через оконные функции или временные таблицы.
  4. Обработка пустых значений: Всегда используйте функцию ЕСТЬNULL при получении ресурсов регистра. Если история цен для товара еще не началась на дату документа, ваш запрос не должен "терять" строку документа или возвращать неопределенные значения в расчетах.

Подведем итог: выбор метода зависит от версии вашей платформы и контекста задачи. Для отчетов идеальным выбором будет СКД, для современных систем на 8.3.20+ — оконные функции, а для универсальности и совместимости со старыми конфигурациями — метод с временными таблицами и группировкой по максимуму периода.

← На главную