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