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

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

Задачи по анализу динамики цен являются одними из самых распространенных в практике разработчика 1С. Часто стандартной виртуальной таблицы СрезПоследних оказывается недостаточно, так как она возвращает только актуальное значение на указанный момент времени. Для расчета процента изменения или построения отчетов по динамике нам необходимо получить «предыдущее» состояние данных. Рассмотрим подробно, как реализовать такой запрос, разберем логику соединений и изучим альтернативные современные способы решения этой задачи.

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

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

Шаг 1. Получение актуальной (последней) цены

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


ВЫБРАТЬ
    ЦеныНоменклатурыСрезПоследних.Номенклатура КАК Номенклатура,
    ЦеныНоменклатурыСрезПоследних.Цена КАК ЦенаПоследняя,
    ЦеныНоменклатурыСрезПоследних.Период КАК ПериодПоследний
ПОМЕСТИТЬ ПоследниеЦены
ИЗ
    РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныНоменклатурыСрезПоследних
;

Шаг 2. Поиск даты предпоследней цены

Теперь проанализируем ситуацию: как нам найти запись, которая была «до» текущей? Нам нужно обратиться к основной (физической) таблице регистра сведений и найти в ней записи, период которых строго меньше периода нашей последней цены. Из всех таких «прошлых» записей самой актуальной (предпоследней) будет та, у которой период максимален.

Рассмотрим код для вычисления даты предпоследней цены:


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

В этом фрагменте мы соединяем наши последние цены с физической таблицей регистра по условию ПоследниеЦены.ПериодПоследний > ЦеныНоменклатуры.Период. Использование функции МАКСИМУМ позволяет нам среди всех старых цен найти именно ту, которая предшествовала последней.

Шаг 3. Сборка итоговых данных и расчет процента

На финальном этапе мы соединяем все полученные данные. Нам нужно получить саму цену для найденной «предпоследней» даты и рассчитать разницу. Обратите внимание на использование функции ЕСТЬNULL и проверку деления на ноль — это критически важно для стабильности запроса.


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

Современный подход: Использование оконных функций (8.3.20+)

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

Выясним, как это работает на практике:

  1. Используем функцию ROW_NUMBER() с разделением (PARTITION BY) по номенклатуре.
  2. Сортируем внутри раздела по периоду в обратном порядке.
  3. Во внешнем запросе просто группируем данные или соединяем их по индексу.

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

// Затем выбираем записи с НомерСтроки = 1 (последняя) и НомерСтроки = 2 (предпоследняя)

Важные нюансы производительности

При работе с большими объемами данных (десятки тысяч позиций номенклатуры и длинная история цен) классический метод через МАКСИМУМ(Период) может работать медленно. Разберем основные рекомендации по оптимизации:

Почему UNION может быть лучше JOIN?

Проанализируем утверждение, что объединение (ОБЪЕДИНИТЬ ВСЕ) работает быстрее. В некоторых сценариях СУБД проще выполнить два независимых поиска и «склеить» результаты, чем выполнять сложное вложенное соединение с условием «больше-меньше». В таком случае мы сначала выбираем последние цены, затем в ту же таблицу через ОБЪЕДИНИТЬ добавляем предпоследние, а в финальном запросе схлопываем их через СГРУППИРОВАТЬ ПО, где цена будет в разных колонках (используя МАКСИМУМ(ВЫБОР КОГДА ЭтоПоследняя ТОГДА Цена ИНАЧЕ 0 КОНЕЦ)).

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

← На главную