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