Как получить цену номенклатуры, выбирая Скидочную цену при ее наличии (не NULL и не 0), иначе Розничную?

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

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

Основная сложность заключается в том, чтобы правильно “приоритезировать” виды цен. Мы хотим, чтобы для каждой позиции номенклатуры и ее характеристики система сначала проверяла наличие скидочной цены. Если скидочная цена есть, и она не равна NULL или 0, то мы используем именно ее. В противном случае, если скидочная цена отсутствует, равна NULL или 0, мы должны обратиться к розничной цене.

Рассмотрим два основных подхода к решению этой задачи в запросах 1С: использование ЛЕВОГО СОЕДИНЕНИЯ с оператором ВЫБОР КОГДА и применение временных таблиц с ОБЪЕДИНЕНИЕМ.

Решение с помощью ЛЕВОГО СОЕДИНЕНИЯ и оператора ВЫБОР КОГДА

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

  1. Получение исходных данных (виртуальная таблица СрезПоследних).

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

    Для каждого экземпляра мы указываем отбор по ВидЦены. Также важно передать параметр &ТекущаяДата, чтобы получить цены на нужный момент времени.

  2. Левое соединение таблиц цен.

    Мы выполним ЛЕВОЕ СОЕДИНЕНИЕ таблицы с розничными ценами (левая таблица) и таблицы со скидочными ценами (правая таблица). Левое соединение выбирает все записи из левой таблицы и соответствующие записи из правой таблицы. Если для записи из левой таблицы нет соответствующей записи в правой, то поля из правой таблицы будут заполнены значением NULL. Это именно то, что нам нужно для проверки наличия скидочной цены.

    Условие соединения должно включать Номенклатура и Характеристика, чтобы связать цены для одного и того же товара с конкретной характеристикой.

  3. Приоритизация с помощью оператора ВЫБОР КОГДА.

    После соединения, в секции ВЫБРАТЬ, мы используем оператор ВЫБОР КОГДА ТОГДА ИНАЧЕ КОНЕЦ. В нем мы проверим скидочную цену:

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

Рассмотрим пример запроса, который реализует данную логику:


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

Пояснения к запросу:

Альтернативное решение с использованием временных таблиц и ОБЪЕДИНЕНИЯ (менее предпочтительное для прямой приоритизации)

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

  1. Получение всех необходимых цен во временную таблицу.

    На первом шаге мы получаем все розничные и скидочные цены на заданную дату и помещаем их во временную таблицу. Это позволяет нам работать с объединенным набором данных на последующих этапах.

  2. Отбор валидных скидочных цен.

    Затем мы создаем вторую временную таблицу, содержащую только скидочные цены, которые не равны NULL и не равны 0. Это будут наши приоритетные цены.

  3. Отбор розничных цен с исключением.

    На третьем шаге мы формируем третью временную таблицу с розничными ценами. Однако мы должны исключить из нее все те позиции номенклатуры (с учетом характеристик), для которых мы уже нашли валидные скидочные цены на предыдущем шаге. Для этого используется оператор НЕ В.

  4. Объединение результатов.

    Наконец, мы объединяем (ОБЪЕДИНИТЬ ВСЕ) две временные таблицы: таблицу с валидными скидочными ценами и таблицу с отфильтрованными розничными ценами. Таким образом, мы получаем финальный список цен с учетом заданного приоритета.

Рассмотрим пример запроса, демонстрирующий этот подход:


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

//////////////////////////////////////////////////////////////////////
// 2. Отбираем валидные скидочные цены
ВЫБРАТЬ
    СкидочныеЦены.Номенклатура КАК Номенклатура,
    СкидочныеЦены.Характеристика КАК Характеристика,
    СкидочныеЦены.ТипЦен КАК ТипЦен,
    МАКСИМУМ(СкидочныеЦены.Цена) КАК Цена
ПОМЕСТИТЬ мСкидочныеЦены
ИЗ
    вВсеЦены КАК СкидочныеЦены
ГДЕ
    СкидочныеЦены.ТипЦен = &ТипЦенСкидка
    И СкидочныеЦены.Цена <> 0
    И СкидочныеЦены.Цена ЕСТЬ НЕ NULL
СГРУППИРОВАТЬ ПО
    СкидочныеЦены.Номенклатура,
    СкидочныеЦены.Характеристика,
    СкидочныеЦены.ТипЦен
;

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

//////////////////////////////////////////////////////////////////////
// 4. Объединяем скидочные цены и оставшиеся розничные
ВЫБРАТЬ
    мСкидочныеЦены.Номенклатура,
    мСкидочныеЦены.Характеристика,
    мСкидочныеЦены.ТипЦен,
    мСкидочныеЦены.Цена
ИЗ
    мСкидочныеЦены КАК мСкидочныеЦены

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    мРозничныеЦены.Номенклатура,
    мРозничныеЦены.Характеристика,
    мРозничныеЦены.ТипЦен,
    мРозничныеЦены.Цена
ИЗ
    мРозничныеЦены КАК мРозничныеЦены

ИТОГИ ПО
    Номенклатура,
    Характеристика

Недостатки данного подхода:

Важные моменты и оптимизации при работе с ценами в 1С

  1. Параметры запроса: Всегда используйте параметры для передачи переменных значений (дата, вид цены). Это предотвращает SQL-инъекции и позволяет системе 1С эффективно кэшировать запросы.

  2. Обработка характеристик: Если часть номенклатуры не имеет характеристик, а часть имеет, учтите это в условиях соединения. Для номенклатуры без характеристик в поле Характеристика обычно хранится ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка). Ваш запрос должен корректно обрабатывать как непустые, так и пустые характеристики.

    Например, в условиях ПО при ЛЕВОМ СОЕДИНЕНИИ можно добавить логику для пустых характеристик, если требуется:

    
    ПО (ЦеныРозничные.Номенклатура = ЦеныСкидочные.Номенклатура
        И ЦеныРозничные.Характеристика = ЦеныСкидочные.Характеристика)
        ИЛИ (ЦеныРозничные.Номенклатура = ЦеныСкидочные.Номенклатура
            И ЦеныРозничные.Характеристика = ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)
            И ЦеныСкидочные.Характеристика = ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка))
    

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

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

  4. Функция МАКСИМУМ: В некоторых случаях (как в примерах из обсуждения), применяется МАКСИМУМ(Цены.Цена). Это может быть полезно, если по каким-то причинам на одну и ту же номенклатуру, характеристику и вид цены может быть несколько записей в срезе последних (что обычно нехарактерно для регистра цен, но может возникнуть при ошибках или специфической логике). Однако, если мы уверены в уникальности цен, эта агрегатная функция может быть излишней.

В целом, для задачи приоритизации цен, где нам нужно выбрать одну цену из нескольких доступных на основе определенных условий (например, "если есть скидка, бери ее, иначе розницу"), метод с ЛЕВЫМ СОЕДИНЕНИЕМ и оператором ВЫБОР КОГДА является наиболее оптимальным и рекомендуемым в 1С.

← На главную