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