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