Как в запросе 1С реализовать выбор цены по приоритетам: от частного к общему

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

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает задача получения актуальной цены для номенклатуры или услуги, когда в системе предусмотрено несколько уровней ценообразования. Типичный пример: для конкретного клиента по конкретному договору действует спеццена, если договора нет — действует цена для этого контрагента, а если нет и цены для контрагента — берется общая цена "для всех".

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

Постановка задачи и логика приоритетов

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

  1. Приоритет 1 (Самый высокий): Совпадение по работе, контрагенту и конкретному договору.
  2. Приоритет 2: Совпадение по работе и контрагенту (договор в регистре не заполнен — пустая ссылка).
  3. Приоритет 3 (Самый низкий): Совпадение только по работе (контрагент и договор не заполнены).

Рассмотрим, как реализовать это в коде запроса.

Способ №1: Использование временных таблиц и группировки по минимальному порядку

Этот метод считается наиболее наглядным и надежным. Мы соберем все возможные цены в одну таблицу, присвоим каждой строке "вес" (порядок), а затем отсечем лишнее. Проанализируем этот алгоритм по шагам.

Шаг 1: Сбор данных с присвоением приоритета. Мы используем оператор ОБЪЕДИНИТЬ ВСЕ, чтобы выбрать данные из среза последних по трем разным условиям.


ВЫБРАТЬ
    Цены.Работа КАК Работа,
    Цены.Цена КАК Цена,
    1 КАК Порядок
ПОМЕСТИТЬ ВТ_ВсеЦены
ИЗ
    РегистрСведений.ЦеныРабот.СрезПоследних(&Дата, Работа В (&МассивРабот) И Контрагент = &Контрагент И Договор = &Договор) КАК Цены

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

ВЫБРАТЬ
    Цены.Работа,
    Цены.Цена,
    2
ИЗ
    РегистрСведений.ЦеныРабот.СрезПоследних(&Дата, Работа В (&МассивРабот) И Контрагент = &Контрагент И Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка)) КАК Цены

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

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

Шаг 2: Определение минимального порядка. Для каждой работы нам нужно оставить только одну запись — ту, у которой число в поле Порядок минимально. Для этого создадим промежуточную таблицу с группировкой.


ВЫБРАТЬ
    ВТ.Работа КАК Работа,
    МИНИМУМ(ВТ.Порядок) КАК Порядок
ПОМЕСТИТЬ ВТ_МинимальныйПорядок
ИЗ
    ВТ_ВсеЦены КАК ВТ
СГРУППИРОВАТЬ ПО
    ВТ.Работа;

Шаг 3: Получение итоговой цены. Теперь соединим таблицу с минимальными порядками обратно с таблицей всех цен, чтобы "вытащить" само значение цены.


ВЫБРАТЬ
    Спр.Ссылка КАК Работа,
    ЕСТЬNULL(ВТ_ВсеЦены.Цена, 0) КАК Цена
ИЗ
    Справочник.Работы КАК Спр
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_МинимальныйПорядок КАК Порядок
    ПО Спр.Ссылка = Порядок.Работа
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВсеЦены КАК ВТ_ВсеЦены
    ПО Порядок.Работа = ВТ_ВсеЦены.Работа 
    И Порядок.Порядок = ВТ_ВсеЦены.Порядок;

Способ №2: Оптимизированное соединение через ЕСТЬNULL ("Матрешка")

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

Выясним преимущество этого метода: он выполняется за один проход по основной таблице, что может быть быстрее на больших объемах данных.


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

    // 2. Если нет по договору, ищем по Контрагенту (с пустым договором)
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныРабот.СрезПоследних(&Период, Контрагент = &Контрагент И Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка)) КАК ЦенаКонтрагент
    ПО Работы.Ссылка = ЦенаКонтрагент.Работа

    // 3. Если нет и по контрагенту, берем общую цену (пустые Контрагент и Договор)
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныРабот.СрезПоследних(&Период, Контрагент = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка) И Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыContragentov.ПустаяСсылка)) КАК ЦенаОбщая
    ПО Работы.Ссылка = ЦенаОбщая.Работа

Анализ производительности и важные рекомендации

При работе с такими запросами важно учитывать особенности индексации в 1С. Рассмотрим ключевые моменты, которые помогут вашему коду работать быстрее:

Подведем итоги

Мы проанализировали два основных подхода к решению задачи. Первый способ с временными таблицами и МИНИМУМ(Порядок) является универсальным "стандартом" 1С-разработки, он легко читается и отлаживается. Второй способ с вложенными ЕСТЬNULL более лаконичен и может выигрывать в производительности при построении больших отчетов или динамических списков подбора.

Выбор конкретного метода зависит от архитектуры вашей конфигурации и объема данных в регистре. Для небольших справочников работ оба метода будут работать практически мгновенно, но для высоконагруженных систем рекомендуется профилировать запрос в консоли и анализировать план выполнения SQL.

← На главную