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