Почему ЛЕВОЕ СОЕДИНЕНИЕ в запросе 1С работает как ВНУТРЕННЕЕ?

Программист 1С v8.3 (Управляемые формы) Управленческий учет Торговля и дистрибуция
← На главную

В разработке на платформе 1С часто возникает задача получить основной набор данных из одной таблицы и дополнить его связанной информацией из другой. Для этого идеально подходит конструкция ЛЕВОЕ СОЕДИНЕНИЕ. Однако начинающие разработчики нередко сталкиваются с ситуацией, когда запрос возвращает не все записи из левой (основной) таблицы, а только те, для которых нашлось соответствие в правой. Фактически, ЛЕВОЕ СОЕДИНЕНИЕ начинает вести себя как ВНУТРЕННЕЕ. Давайте разберем, почему так происходит и как этого избежать, ведь правильное написание запросов — это залог того, что быстрый анализ данных будет точным и эффективным.

Проанализируем типичную ситуацию, которая часто встречается как тестовое задание на должность 1С мидл: есть запрос, который должен вывести все дни из производственного календаря за определенный период, а для тех дней, когда были движения по товарам на складе, — показать эти движения. Если движений не было, мы все равно хотим видеть дату в результате. Но почему-то даты без движений пропадают.

Анализ проблемы: как ГДЕ превращает ЛЕВОЕ соединение во ВНУТРЕННЕЕ

Рассмотрение подобных нюансов часто включает в себя собеседование или отбор кандидата-программиста. Рассмотрим исходный код запроса, который привел к проблеме. Задача — получить все даты из регистра сведений ДанныеПроизводственногоКалендаря и присоединить к ним данные из регистра накопления ТоварыНаСкладах.

Неправильный вариант запроса:


ВЫБРАТЬ
	ДанныеПроизводственногоКалендаря.Дата КАК Дата,
	ДанныеПроизводственногоКалендаря.ВидДня КАК ВидДня
ИЗ
	РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.ОстаткиИОбороты(&НачалоПериода, &КонецПериода, День, , ) КАК ТоварыНаСкладахОстаткиИОбороты
		ПО (ДанныеПроизводственногоКалендаря.Дата = ТоварыНаСкладахОстаткиИОбороты.Период)
ГДЕ
	ДанныеПроизводственногоКалендаря.Дата МЕЖДУ &НачалоПериода И &КонецПериода
	И ТоварыНаСкладахОстаткиИОбороты.Номенклатура = &Н

На первый взгляд, все логично. Мы берем все даты, соединяем их с остатками и оборотами, а затем в секции ГДЕ фильтруем результат по нужной номенклатуре. Но именно здесь и кроется ошибка. Чтобы быстро обнаружить подобные проблемы в сложных алгоритмах, разработчиками часто используется Консоль запросов с поддержкой пакетных запросов. Давайте разберем по шагам, как СУБД выполняет этот запрос:

  1. Выполнение соединения. Сначала выполняется ЛЕВОЕ СОЕДИНЕНИЕ. Система берет абсолютно все записи из левой таблицы (ДанныеПроизводственногоКалендаря) за указанный период. Затем для каждой записи она ищет соответствие в правой таблице (виртуальной таблице ТоварыНаСкладах.ОстаткиИОбороты) по условию ПО (ДанныеПроизводственногоКалендаря.Дата = ТоварыНаСкладахОстаткиИОбороты.Период).
  2. Обработка несоответствий. Если для какой-то даты из календаря не нашлось ни одной записи в регистре товаров, то ЛЕВОЕ СОЕДИНЕНИЕ все равно оставит эту строку в промежуточном результате. Однако все поля, относящиеся к правой таблице, для этой строки будут заполнены специальным значением NULL.
  3. Применение фильтра ГДЕ. И только после того, как соединение выполнено, применяется фильтрация. Проводя анализ конфигураций на наличие ошибок, можно заметить, что это самая частая причина некорректных выборок в отчетах.

В строках, где соответствия не нашлось, поле ТоварыНаСкладахОстаткиИОбороты.Номенклатура равно NULL. Любая операция сравнения со значением NULL (кроме ЕСТЬ NULL) дает результат ЛОЖЬ. Таким образом, условие NULL = &Н никогда не будет истинным, и записи отсекаются.

Правильное решение: перенос условия в параметры соединения

Чтобы сохранить логику ЛЕВОГО СОЕДИНЕНИЯ, все условия, относящиеся к присоединяемой (правой) таблице, нужно применять до или во время самого соединения. Стоит отметить, что в новых версиях платформы (8.3.25+) появилось даже добавление данных в существующую временную таблицу, что еще больше расширяет возможности оптимизации запросов, но база остается прежней: фильтруйте правую таблицу заранее.

Исправленный и корректный вариант запроса:


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

Как теперь работает запрос:

  1. Система обращается к виртуальной таблице и сразу применяет к ней фильтр Номенклатура = &Н.
  2. Далее выполняется ЛЕВОЕ СОЕДИНЕНИЕ всех записей календаря с уже отфильтрованным набором данных.
  3. Если для какой-то даты соответствия нет, строка из календаря все равно остается в результате, а поля из правой таблицы заполняются NULL.
  4. Финальная секция ГДЕ не отсекает эти строки, так как она фильтрует только левую таблицу.

Таким образом, мы получаем корректную работу ЛЕВОГО СОЕДИНЕНИЯ. Запомните простое правило: при использовании левого соединения условия для основной таблицы помещаются в ГДЕ, а условия для правой — в секцию ПО или в параметры виртуальной таблицы.

← На главную