Как в запросе 1С реализовать выбор данных с приоритетом (конкретное значение или по умолчанию)

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

В практике разработки на платформе 1С часто возникает задача выбора наиболее подходящего значения из регистра. Классический пример — определение цены или тарифа. У нас может быть задан «общий» тариф для должности и «специальный» тариф для той же должности, но в рамках конкретного проекта. В этой статье мы подробно разберем, как построить запрос, который будет корректно выбирать уточненный тариф при его наличии и переключаться на базовый, если уточнений нет.

Постановка задачи и анализ исходных данных

Представим, что у нас есть непериодический регистр сведений Тарифы. В нем три измерения: Должность, Проект и один ресурс Тариф. Ключевая особенность структуры в том, что поле Проект может быть пустым. Если оно пустое — это тариф «по умолчанию» для данной должности. Если заполнено — это тариф для конкретного проекта.

На входе мы имеем временную таблицу ДанныеВТ с колонками Должность, Проект и КоличествоЧасов. Наша цель — рассчитать стоимость, умножив часы на правильный тариф.

Проанализируем пример данных:

Решение методом двойного левого соединения

Самый эффективный и распространенный способ решения этой задачи — использование двух левых соединений к таблице тарифов. Первое соединение ищет точное совпадение (Должность + Проект), а второе — только по должности с условием, что проект в регистре пуст.

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


ВЫБРАТЬ
    ДанныеВТ.Должность КАК Должность,
    ДанныеВТ.Проект КАК Проект,
    ДанныеВТ.КоличествоЧасов КАК КоличествоЧасов,
    // Выбираем сначала уточненный тариф, если его нет (NULL) — берем базовый
    ЕСТЬNULL(ТарифПоПроекту.Тариф, ТарифБазовый.Тариф) КАК АктуальныйТариф
ПОМЕСТИТЬ ДанныеСТарифами
ИЗ
    ДанныеВТ КАК ДанныеВТ
        // Первое соединение: ищем точный тариф для конкретного проекта
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Тарифы КАК ТарифПоПроекту
        ПО ДанныеВТ.Должность = ТарифПоПроекту.Должность
            И ДанныеВТ.Проект = ТарифПоПроекту.Проект
        
        // Второе соединение: ищем базовый тариф (где проект не заполнен)
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Тарифы КАК ТарифБазовый
        ПО ДанныеВТ.Должность = ТарифБазовый.Должность
            И (ТарифБазовый.Проект = ЗНАЧЕНИЕ(Справочник.Проекты.ПустаяСсылка))
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ДанныеСТарифами.Должность,
    ДанныеСТарифами.Проект,
    ДанныеСТарифами.КоличествоЧасов,
    ДанныеСТарифами.КоличествоЧасов * ДанныеСТарифами.АктуальныйТариф КАК Стоимость
ИЗ
    ДанныеСТарифами КАК ДанныеСТарифами

Разберем важные моменты этого кода. В первом соединении мы сравниваем ДанныеВТ.Проект = ТарифПоПроекту.Проект. Если в данных проект заполнен, а в регистре для этой должности есть и запись с проектом, и запись без проекта, это соединение выберем именно "проектную" запись.

Во втором соединении мы жестко ограничиваем выборку из регистра только теми записями, где проект пустой: ЗНАЧЕНИЕ(Справочник.Проекты.ПустаяСсылка). Это позволяет нам всегда иметь "под рукой" базовую ставку, если она существует.

Функция ЕСТЬNULL здесь работает как переключатель приоритета. Если ТарифПоПроекту.Тариф вернул NULL (запись не найдена), система берет значение из ТарифБазовый.Тариф.

Альтернативный метод: Объединение и Группировка

В некоторых случаях, когда уровней вложенности больше (например: Тариф для проекта -> Тариф для группы проектов -> Тариф для всех), метод левых соединений становится громоздким. Рассмотрим альтернативный подход с использованием «весов» или приоритетов.

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

  1. Выбираем данные и соединяем их со всеми подходящими тарифами (и по проекту, и по пустой ссылке).
  2. Для записи с проектом назначаем Приоритет = 1, для записи без проекта Приоритет = 0.
  3. Группируем результат, оставляя только тот тариф, у которого приоритет выше.

Посмотрим на пример реализации этого метода:


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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Т.Должность,
    Т.Проект,
    Т.КоличествоЧасов,
    МАКСИМУМ(Т.Приоритет) КАК МаксПриоритет
ПОМЕСТИТЬ ТолькоНужныеПриоритеты
ИЗ
    ВсеВозможныеТарифы КАК Т
ГРУППИРОВАТЬ ПО
    Т.Должность, Т.Проект, Т.КоличествоЧасов
;

////////////////////////////////////////////////////////////////////////////////
// Финальное соединение для получения тарифа по максимальному приоритету
ВЫБРАТЬ
    Т.Должность, Т.Проект, Т.КоличествоЧасов, Т.Тариф * Т.КоличествоЧасов КАК Стоимость
ИЗ
    ВсеВозможныеТарифы КАК Т
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ТолькоНужныеПриоритеты КАК Пр
        ПО Т.Должность = Пр.Должность 
            И Т.Проект = Пр.Проект 
            И Т.Приоритет = Пр.МаксПриоритет

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

При реализации подобных запросов программисты часто допускают ошибки в работе с типами данных и индексами. Проанализируем основные моменты:

1. NULL против Пустой ссылки. Помните, что NULL — это отсутствие записи в базе данных после левого соединения. ПустаяСсылка — это конкретное значение (объект), сохраненное в базе. В нашем условии соединения мы используем ЗНАЧЕНИЕ(Справочник.Проекты.ПустаяСсылка), чтобы найти запись, которую пользователь сохранил в регистре, не указав проект.

2. Индексация. Чтобы запрос работал быстро на больших объемах данных, измерения Должность и Проект в регистре Тарифы должны быть проиндексированы. В 1С измерения индексируются автоматически, но порядок их следования в ключе важен. Оптимально, если Должность стоит первым в списке измерений, так как она используется во всех условиях соединения.

3. Использование ВЫБОР КОГДА. Иногда вместо ЕСТЬNULL в основном списке полей используют конструкцию ВЫБОР. Это допустимо, но ЕСТЬNULL работает быстрее на уровне SQL-сервера и делает код более читаемым.

4. Обработка отсутствия базового тарифа. Выясним, что произойдет, если для должности нет вообще никакой записи в регистре. В этом случае оба левых соединения вернут NULL. Рекомендуется оборачивать финальный расчет стоимости в еще один ЕСТЬNULL(..., 0), чтобы избежать ошибок при умножении на неопределенное значение.

Заключение

Мы разобрали классическую задачу на приоритеты в запросах 1С. Метод с двумя левыми соединениями является «золотым стандартом» для простых случаев с одним уровнем уточнения. Он нагляден, производителен и легко отлаживается. Если же ваша система предполагает сложную иерархию (например, тарифы по регионам, городам и конкретным точкам), рекомендуем смотреть в сторону метода с весами приоритетов.

← На главную