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