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