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

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

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

Рассмотрим подробнее типовую ситуацию: в запросе необходимо сопоставить данные табличной части документа со срезом последних цен по двум разным типам и вывести их в отдельные колонки результата. Разберем различные подходы к реализации, их плюсы, минусы и ключевые оптимизации.

Способ 1. Несколько левых соединений с виртуальной таблицей СрезПоследних

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

Посмотрим на практический пример реализации такого запроса:


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

Проанализируем ключевые моменты этой конструкции:

  1. Разделение псевдонимов таблиц и полей: Для каждого подключения регистра используются независимые имена таблиц (ЦеныСтарые, ЦеныНовые) и алиасы результирующих колонок (ЦенаСтарая, ЦенаНовая). Это исключает неоднозначность в секции ВЫБРАТЬ.
  2. Параметры виртуальной таблицы: Условие по типу цен передается именно во второй параметр виртуальной таблицы (ТипЦен = &ТипЦен...), а не в секцию ГДЕ или ПО. Это критично для производительности, так как платформа вычисляет итоги только по указанному измерению.
  3. Обработка пустых значений: Конструкция ЕСТЬNULL защищает отчет от появления значений NULL, если по конкретной позиции цена заданного типа ранее не была установлена.

Ограничения производительности и оптимизированный подход

Несмотря на простоту первого способа, при больших объемах базы данных у него есть существенный недостаток. Виртуальная таблица СрезПоследних формирует тяжелый подзапрос с группировкой и расчетом максимума по периоду. Когда мы делаем два независимых соединения с этой таблицей, СУБД выполняет расчет дважды, причем по всему объему регистра для указанных типов цен.

Чтобы ускорить выполнение запроса и соблюсти стандарты разработки платформы 1С, применим оптимизированную схему через временные таблицы и разворот строк.

Разберем по шагам правильную архитектуру такого запроса:

  1. Подготовка исходных данных: Выгружаем уникальные пары Номенклатура и ХарактеристикаНоменклатуры из документа во временную таблицу и индексируем их.
  2. Фильтрация среза: Обращаемся к срезу последних один раз, передав массив типов цен ТипЦен В (&МассивТиповЦен) и ограничив срез только позициями из временной таблицы документа через условие (Номенклатура, ХарактеристикаНоменклатуры) В (ВЫБРАТЬ ...).
  3. Разворот цен по колонкам: С помощью конструкции ВЫБОР КОГДА и агрегатной функции МАКСИМУМ переводим строки регистра в нужные колонки.

Посмотрим, как выглядит оптимизированный пакетный запрос:


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

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Товары.Номенклатура КАК Номенклатура,
    Товары.ХарактеристикаНоменклатуры КАК ХарактеристикаНоменклатуры,
    Товары.ЦенаДокумента КАК ЦенаДокумента,
    ЕСТЬNULL(Цены.ЦенаСтарая, 0) КАК ЦенаСтарая,
    ЕСТЬNULL(Цены.ЦенаНовая, 0) КАК ЦенаНовая
ИЗ
    ВТ_ТоварыДокумента КАК Товары
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ЦеныСвернутые КАК Цены
        ПО Товары.Номенклатура = Цены.Номенклатура
        И Товары.ХарактеристикаНоменклатуры = Цены.ХарактеристикаНоменклатуры

Важные практические нюансы

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

  1. Корректная дата среза цен: Если срез берется в момент проведения документа для получения «старой» цены, использование чистой даты документа может привести к считыванию движений, сформированных в эту же секунду. Чтобы получить срез строго до текущего документа, необходимо передавать объект Граница:
    
    ГраницаСреза = Новый Граница(ДокументСсылка.МоментВремени(), ВидГраницы.Исключая);
    Запрос.УстановитьПараметр("ДатаСреза", ГраницаСреза);
    
  2. Отказ от запросов в цикле: В типовых конфигурациях существует функция Ценообразование.ПолучитьЦенуНоменклатуры(). Однако ее вызов для каждой строки документа при проведении или формировании отчета порождает антипаттерн «запрос в цикле». Обработка табличной части единым пакетным запросом всегда предпочтительнее по скорости.
  3. Единицы измерения и валюты: В регистре сведений ЦеныНоменклатуры значения хранятся за базовую единицу измерения и в валюте типа цен. Если документ оформлен в валюте или упаковках (с коэффициентом), значения цен перед сравнением необходимо пересчитывать с учетом реквизитов Коэффициент и курса валюты из регистра сведений КурсыВалют.
← На главную