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