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