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

Программист 1С:Зарплата и Управление Персоналом Управленческий учет
← На главную

В процессе работы с запросами в 1С часто возникает задача: для одного и того же объекта аналитики (например, сотрудника) существует несколько записей, созданных разными документами, и нам нужно выбрать только одну из них по определенному правилу. Классический пример — когда по сотруднику есть и документ «Начисление зарплаты», и документ «Отпуск». Задача — если есть «Отпуск», то брать данные только из него, а «Начисление зарплаты» игнорировать. Если же «Отпуска» нет, то нужно выбрать «Начисление зарплаты».

Рассмотрим, как решить эту задачу правильно, и разберем, почему простые условия в запросе не приводят к желаемому результату.

Проблема: почему простое условие ВЫБОР КОГДА не работает?

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

Проанализируем причину. Оператор ВЫБОР, как и вся секция ГДЕ, обрабатывает каждую запись (строку) таблицы индивидуально. Когда система анализирует строку с документом «Начисление зарплаты», она не имеет информации о том, существует ли для этого же сотрудника другая строка с документом «Отпуск». Запрос не "видит" всю совокупность данных по сотруднику в момент обработки одной строки. Поэтому невозможно в рамках одной строки принять решение, основанное на наличии других строк.

Попытки создать сложные логические условия вроде «Если тип документа Отпуск, ТОГДА условие А, ИНАЧЕ условие Б» в одной секции ГДЕ обречены на провал, так как они не могут анализировать контекст (другие записи по этому же сотруднику).

Решение 1: Использование временных таблиц (рекомендуемый способ)

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

Разберем по шагам на нашем примере.

  1. Шаг 1. Выбираем все нужные записи и присваиваем им приоритет.

    Первым запросом пакета мы получаем все начисления по сотрудникам из нужных нам документов («Отпуск», «НачислениеЗарплаты» и др.) и помещаем их во временную таблицу. Ключевой момент здесь — мы добавляем вычисляемое поле, назовем его Приоритет. Чем ниже значение в этом поле, тем выше приоритет документа.

    Например, присвоим документу «Отпуск» приоритет 1 (самый высокий), а документу «НачислениеЗарплаты» — 2.

    
    ВЫБРАТЬ
        Начисления.Сотрудник,
        Начисления.Регистратор,
        Начисления.Сумма,
        ВЫБОР
            КОГДА Начисления.Регистратор ССЫЛКА Документ.Отпуск
                ТОГДА 1
            КОГДА Начисления.Регистратор ССЫЛКА Документ.НачислениеЗарплаты
                ТОГДА 2
            ИНАЧЕ 99
        КОНЕЦ КАК Приоритет
    ПОМЕСТИТЬ ВТ_ВсеНачисления
    ИЗ
        РегистрРасчета.Начисления КАК Начисления
    ГДЕ
        Начисления.Регистратор ССЫЛКА Документ.Отпуск
        ИЛИ Начисления.Регистратор ССЫЛКА Документ.НачислениеЗарплаты
    
  2. Шаг 2. Находим наивысший приоритет для каждого сотрудника.

    Вторым запросом мы группируем данные из созданной временной таблицы ВТ_ВсеНачисления по полю Сотрудник. С помощью агрегатной функции МИНИМУМ() мы находим наименьшее значение приоритета для каждого сотрудника. Результат помещаем во вторую временную таблицу.

    Если у сотрудника есть «Отпуск» (приоритет 1) и «НачислениеЗарплаты» (приоритет 2), то минимум будет равен 1. Если только «НачислениеЗарплаты», то минимум будет 2.

    
    ВЫБРАТЬ
        ВТ_ВсеНачисления.Сотрудник,
        МИНИМУМ(ВТ_ВсеНачисления.Приоритет) КАК Приоритет
    ПОМЕСТИТЬ ВТ_Приоритеты
    ИЗ
        ВТ_ВсеНачисления КАК ВТ_ВсеНачисления
    СГРУППИРОВАТЬ ПО
        ВТ_ВсеНачисления.Сотрудник
    
  3. Шаг 3. Выбираем итоговые данные.

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

    
    ВЫБРАТЬ
        ВТ_ВсеНачисления.Сотрудник,
        ВТ_ВсеНачисления.Регистратор,
        ВТ_ВсеНачисления.Сумма
    ИЗ
        ВТ_ВсеНачисления КАК ВТ_ВсеНачисления
            ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Приоритеты КАК ВТ_Приоритеты
            ПО ВТ_ВсеНачисления.Сотрудник = ВТ_Приоритеты.Сотрудник
            И ВТ_ВсеНачисления.Приоритет = ВТ_Приоритеты.Приоритет
    

Этот подход является надежным, гибким (можно легко добавить больше уровней приоритета) и хорошо читается.

Решение 2: Использование вложенного запроса в условии

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

Логика условия будет следующей:

Реализовать проверку на существование можно через конструкцию НЕ В или НЕ СУЩЕСТВУЕТ.

Посмотрим на пример с НЕ В:


ВЫБРАТЬ
    Начисления.Сотрудник,
    Начисления.Регистратор,
    Начисления.Сумма
ИЗ
    РегистрРасчета.Начисления КАК Начисления
ГДЕ
    (Начисления.Регистратор ССЫЛКА Документ.Отпуск)
    ИЛИ (
        Начисления.Регистратор ССЫЛКА Документ.НачислениеЗарплаты
        И Начисления.Сотрудник НЕ В (
            ВЫБРАТЬ
                Отпуска.Сотрудник
            ИЗ
                РегистрРасчета.Начисления КАК Отпуска
            ГДЕ
                Отпуска.Регистратор ССЫЛКА Документ.Отпуск
        )
    )

Такой запрос сложнее для чтения и отладки, а также может выполняться медленнее, так как для каждой строки с начислением зарплаты будет выполняться вложенный подзапрос. Поэтому метод с временными таблицами является более предпочтительным.

Полезные синтаксические конструкции для условий

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

← На главную