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

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

В практике разработки на платформе 1С часто возникает задача получения не просто актуальной цены на текущий момент, а именно «прошлой» цены — той, которая предшествовала действующей. Это необходимо для анализа динамики цен, формирования отчетов об изменении прайс-листа или для автоматического расчета скидок. В рамках данной статьи мы подробно разберем несколько подходов к решению этой задачи, проанализируем их эффективность и научимся писать оптимальные запросы к регистру сведений ЦеныНоменклатуры.

Метод 1: Использование временных таблиц и последовательного поиска максимальных дат

Этот метод считается наиболее «профессиональным» и стабильным с точки зрения производительности SQL-сервера. Его суть заключается в том, чтобы поэтапно отсекать самые свежие записи, пока мы не доберемся до нужной нам «предпоследней» цены. Разберем этот алгоритм по шагам.

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

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

Посмотрим на пример реализации такого подхода в коде запроса:


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

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ПредДаты.Номенклатура,
    ПредДаты.ВидЦены,
    ЦеныРегистр.Цена КАК ПрошлаяЦена
ИЗ
    ВТ_ПредпоследниеДаты КАК ПредДаты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныРегистр
        ПО ПредДаты.Номенклатура = ЦеныРегистр.Номенклатура
            И ПредДаты.ВидЦены = ЦеныРегистр.ВидЦены
            И ПредДаты.ПредыдущийПериод = ЦеныРегистр.Период

Метод 2: Соединение виртуальной таблицы СрезПоследних с физической таблицей

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

Важный нюанс: Этот метод хорошо работает на небольших объемах данных. Однако, если в вашем регистре сотни тысяч записей, прямое соединение «всех со всеми» может вызвать существенные задержки. Выясним причину: SQL-серверу придется выполнять тяжелое сканирование индексов для поиска подходящих под неравенство строк.

Рассмотрим пример такого запроса:


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

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

Метод 3: СрезПоследних «на секунду назад»

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

Если мы уже знаем момент времени (дату и время) установки текущей цены, мы можем запросить срез последних на момент ДОБАВИТЬКДАТЕ(ДатаТекущейЦены, СЕКУНДА, -1). Платформа 1С автоматически вернет ту запись, которая была актуальна до этой секунды. Это самый быстрый способ, так как он использует оптимизированные механизмы виртуальных таблиц.

Учет уникальности и момента времени

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

Если периодичность регистра установлена в значение «В пределах позиции регистратора», то для однозначного определения последовательности записей следует использовать МоментВремени. В запросах это реализуется через сравнение комбинации Период и Регистратор. Однако в большинстве типовых конфигураций (таких как УТ 11 или ERP) для цен номенклатуры используется периодичность «День» или «Секунда», что упрощает задачу.

Обработка пустых значений (NULL)

Рассмотрим еще один важный аспект: что произойдет, если у товара всего одна запись в истории цен? В этом случае «предыдущей» цены просто не существует. При использовании ВНУТРЕННЕГО СОЕДИНЕНИЯ (INNER JOIN) такой товар полностью исчезнет из итоговой выборки.

Чтобы избежать этой проблемы и видеть в отчете все товары (даже те, у которых не было прошлых цен), необходимо использовать ЛЕВОЕ СОЕДИНЕНИЕ. При этом поле с прошлой ценой может содержать NULL. Рекомендуется использовать функцию ЕСТЬNULL(Цена, 0), чтобы в результате выводился ноль вместо пустого значения.

Практические советы по оптимизации

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

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

← На главную