При работе с запросами в системе 1С:Предприятие разработчики часто сталкиваются с ситуацией, когда использование ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN) не приносит ожидаемого результата. Вместо того чтобы получить все записи из основной таблицы и дополнить их данными из вторичной, запрос возвращает только те строки, которые есть в обеих таблицах. Фактически левое соединение начинает работать как ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Разберем детально, почему это происходит и как правильно проектировать такие запросы.
Рассмотрим типичный пример. Нам необходимо выбрать все документы ТребованиеНакладная и для каждого из них попытаться получить значение дополнительного реквизита "Причина" из регистра сведений ЗначенияСвойствОбъектов. Если у документа нет такого реквизита, мы все равно хотим видеть сам документ в списке, но с пустым значением свойства.
Посмотрим на ошибочный вариант запроса, который часто пишут новички:
"ВЫБРАТЬ
| ТребованиеНакладная.Номер КАК Номер,
| ЗначенияСвойствОбъектов.Значение КАК Значение
|ИЗ
| Документ.ТребованиеНакладная КАК ТребованиеНакладная
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
| ПО ТребованиеНакладная.Ссылка = ЗначенияСвойствОбъектов.Объект
|ГДЕ
| ТребованиеНакладная.Ссылка = &ТН
| И ЗначенияСвойствОбъектов.Объект = &ТН
| И ЗначенияСвойствОбъектов.Свойство = &Причина";
Проанализируем, почему этот запрос не вернет документ, если в регистре нет записи по свойству "Причина". Основная причина кроется в секции ГДЕ. Когда мы накладываем условие на поля таблицы, которая находится в "правой" части левого соединения (в нашем случае это ЗначенияСвойствОбъектов), СУБД сначала выполняет соединение, а затем применяет фильтр ГДЕ ко всему результату.
Если в регистре нет подходящей записи, то после соединения в полях регистра будет значение NULL. Однако условие ЗначенияСвойствОбъектов.Свойство = &Причина не может быть истинным для NULL. В итоге строка отсекается фильтром, и мы теряем данные основной таблицы ТребованиеНакладная.
Чтобы сохранить логику левого соединения, необходимо перенести все условия, касающиеся правой таблицы, непосредственно в условие соединения ПО. Выясним, как изменится текст запроса:
"ВЫБРАТЬ
| ТребованиеНакладная.Номер КАК Номер,
| ЗначенияСвойствОбъектов.Значение КАК Значение
|ИЗ
| Документ.ТребованиеНакладная КАК ТребованиеНакладная
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
| ПО ТребованиеНакладная.Ссылка = ЗначенияСвойствОбъектов.Объект
| И ЗначенияСвойствОбъектов.Свойство = &Причина
|ГДЕ
| ТребованиеНакладная.Ссылка = &ТН";
Рассмотрим, что изменилось. Теперь условие по свойству "Причина" проверяется в момент соединения. Система берет документ, ищет в регистре запись, которая соответствует ссылке документа И имеет нужное свойство. Если такая запись найдена — данные подставляются. Если не найдена — документ остается в выборке, а поля регистра заполняются NULL. Секция ГДЕ теперь фильтрует только основную таблицу (по ссылке на документ), не влияя на результат соединения.
Часто в интерфейсе или отчетах вместо пустого значения (NULL) логично вывести какой-то текст-заглушку или значение по умолчанию. Для этого воспользуемся функцией ЕСТЬNULL. Разберем по шагам реализацию в секции ВЫБРАТЬ:
"ВЫБРАТЬ
| ТребованиеНакладная.Номер КАК Номер,
| ЕСТЬNULL(ЗначенияСвойствОбъектов.Значение, ""Не указано"") КАК ЗначениеПричины
|ИЗ
| Документ.ТребованиеНакладная КАК ТребованиеНакладная
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
| ПО ТребованиеНакладная.Ссылка = ЗначенияСвойствОбъектов.Объект
| И ЗначенияСвойствОбъектов.Свойство = &Причина
|ГДЕ
| ТребованиеНакладная.Ссылка = &ТН";
Функция ЕСТЬNULL проверяет первый параметр. Если там NULL, она возвращает второй параметр. Это отличный способ сделать отчет более читаемым для пользователя.
Для глубокого понимания работы запросов полезно запомнить логический порядок обработки команд СУБД (хотя оптимизатор может менять физический порядок для ускорения, логика остается неизменной):
Из этого списка видно, что ON выполняется раньше, чем WHERE. Поэтому фильтры в ON ограничивают только присоединяемую "правую" таблицу, а фильтры в WHERE — весь результат уже соединенной таблицы.
Рассмотрим несколько важных нюансов, которые помогут писать не только правильные, но и быстрые запросы:
1. Использование индексов. В регистре ЗначенияСвойствОбъектов поле Объект обычно является ведущим полем кластерного индекса. Когда мы пишем ПО ТребованиеНакладная.Ссылка = ЗначенияСвойствОбъектов.Объект, система очень эффективно находит данные. Если же мы оставим фильтрацию по свойству в секции ГДЕ, СУБД может быть сложнее использовать индекс оптимально, так как ей придется сначала соединить таблицы (возможно, получив много лишних данных), а потом их фильтровать.
2. Временные таблицы. В крупных базах (с миллионами записей) прямое левое соединение к большим регистрам может замедлять работу. Профессиональным подходом считается использование временных таблиц. Сначала выберем нужные нам ссылки на документы во временную таблицу ВТ_Документы, а затем соединим ее с регистром. Это позволит оптимизатору запросов построить более стабильный план выполнения.
3. Проблема размножения строк. Помните, что ЛЕВОЕ СОЕДИНЕНИЕ может увеличить количество строк в результате. Если для одного документа в регистре по какой-то причине окажется две записи с одинаковым свойством, документ в результате запроса продублируется. Всегда проверяйте уникальность данных в присоединяемых таблицах или используйте агрегатные функции (например, МАКСИМУМ), если вам нужно гарантированно получить только одно значение.
4. Виртуальные таблицы. Если вы работаете не с физическим регистром сведений, а с виртуальной таблицей (например, СрезПоследних), всегда передавайте условия фильтрации в параметры виртуальной таблицы. Это работает еще быстрее, чем фильтрация в секции ПО, так как данные отсекаются еще на этапе формирования виртуальной таблицы платформой 1С.
Подведем итог: использование условий в секции ПО при левом соединении — это не просто "хитрость", а стандарт грамотной разработки в 1С. Это гарантирует целостность вашей выборки и высокую производительность системы.