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

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

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

Почему нельзя использовать СрезПоследних в таких запросах

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

Выясним причину дублей в запросе автора: при условии Период < ВложенныйЗапрос.Период для каждой записи из истории подтягивается актуальная цена из среза, но группировка по цене не позволяет корректно отсечь ненужные данные. Для работы с историей изменений нужно использовать только физические таблицы регистра сведений.

Способ 1. Использование коррелированного подзапроса

Рассмотрим наиболее простой с точки зрения написания кода способ. Он заключается в том, чтобы прямо в списке выбора полей основного запроса описать подзапрос, который будет искать предыдущее значение для каждой конкретной строки. Этот метод хорошо подходит, если объем данных в выборке невелик (например, отчет по одной номенклатуре).

Разберем по шагам логику этого запроса:


ВЫБРАТЬ
    Цены.Номенклатура КАК Номенклатура,
    Цены.Период КАК Период,
    Цены.Цена КАК ТекущаяЦена,
    (ВЫБРАТЬ ПЕРВЫЕ 1
        ПредЦена.Цена
    ИЗ
        РегистрСведений.ЦеныНоменклатурыПоставщиков КАК ПредЦена
    ГДЕ
        ПредЦена.Номенклатура = Цены.Номенклатура
        И ПредЦена.Период < Цены.Период
    УПОРЯДОЧИТЬ ПО
        ПредЦена.Период УБЫВ) КАК ПредыдущаяЦена
ИЗ
    РегистрСведений.ЦеныНоменклатурыПоставщиков КАК Цены
ГДЕ
    Цены.Номенклатура = &Номенклатура

В данном примере для каждой строки из таблицы Цены система выполняет поиск одной (ПЕРВЫЕ 1) записи, которая была создана ранее текущей, и берет её цену. Важно использовать сортировку УПОРЯДОЧИТЬ ПО Период УБЫВ, чтобы получить именно ближайшую предыдущую запись, а не самую первую в истории.

Способ 2. Классическая схема через временные таблицы

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

Рассмотрим алгоритм реализации:

  1. Создание базовой таблицы. Выбираем все необходимые записи из регистра во временную таблицу ВТ_Цены. Это позволит нам работать с подготовленным набором данных.
  2. Поиск предыдущих периодов. Соединяем таблицу ВТ_Цены саму с собой (левое соединение), чтобы найти для каждой даты все даты, которые меньше неё. С помощью группировки находим МАКСИМУМ из этих меньших дат.
  3. Получение значений. Соединяем результат с исходными данными для получения сумм.

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

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

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

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

Способ 3. Современный подход: Оконные функции (Платформа 8.3.20+)

Если вы работаете на современной версии платформы 8.3.20 и выше, задачу можно решить в один этап с помощью оконных функций. Это самый быстрый и лаконичный способ. Мы используем функцию ЛАГ (LAG), которая позволяет "заглянуть" в предыдущую строку результата запроса.

Посмотрим на пример кода:


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

Здесь функция ЛАГ автоматически берет значение поля Цена из строки, которая предшествует текущей согласно сортировке по периоду. Это избавляет нас от необходимости делать какие-либо соединения вообще.

Способ 4. Реализация в Системе компоновки данных (СКД)

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

Разберем пример выражения для поля "Предыдущая цена":

ВычислитьВыражение("Цена", , , "Предыдущая", "Текущая")

Это выражение приказывает системе компоновки взять значение Цена из предыдущей записи в текущей группировке. Это позволяет оставить текст запроса максимально простым: ВЫБРАТЬ Период, Номенклатура, Цена ИЗ РегистрСведений.ЦеныНоменклатурыПоставщиков.

Важные нюансы и рекомендации

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

  1. Одинаковые периоды. Если в регистре возможны несколько записей с абсолютно одинаковым временем (например, в пределах одной секунды), условие Период < ПредПериод может сработать неоднозначно. В таких случаях в соединениях и сортировках рекомендуется дополнительно использовать поле НомерСтроки или уникальный идентификатор регистратора.
  2. Индексация. При использовании временных таблиц (Способ 2) обязательно индексируйте временные таблицы по полям Номенклатура и Период. Это критически важно для скорости работы левых соединений.
  3. Левое соединение. Всегда используйте ЛЕВОЕ СОЕДИНЕНИЕ при получении предыдущей цены. Для самой первой записи в истории предыдущей цены не существует, и при использовании ВНУТРЕННЕГО СОЕДИНЕНИЯ эта запись просто пропадет из результата.

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

← На главную