В практике разработчика 1С часто возникает задача: найти объекты в основной таблице (например, в справочнике Контрагенты), для которых не заведено ни одной записи в подчиненной таблице (например, в справочнике ИнформационныеБазы или в определенном регистре сведений). На первый взгляд задача кажется простой, но именно на таких «очевидных» вещах часто случается логический ступор. Давайте подробно разберем, как правильно составить такой запрос, какие существуют нюансы и как оптимизировать производительность при больших объемах данных.
Это самый распространенный и эффективный способ решения задачи. Разберем логику: мы берем всех контрагентов и пытаемся «приклеить» к ним записи из подчиненного справочника. Если у контрагента есть подчиненные элементы, поля из правой таблицы заполнятся данными. Если подчиненных элементов нет, поля правой таблицы примут значение NULL. Нам остается лишь отфильтровать результат, оставив только те строки, где в правой части стоит «пустота».
Рассмотрим пошагово составление такого запроса:
Справочник.Контрагенты.ЛЕВОЕ СОЕДИНЕНИЕ к подчиненной таблице — Справочник.ИнформационныеБазы.Контрагенты.Ссылка = ИнформационныеБазы.Владелец.ГДЕ, в котором проверяем ссылку из подчиненной таблицы на ЕСТЬ NULL.Проанализируем пример кода для нашей задачи:
ВЫБРАТЬ
Контрагенты.Ссылка КАК Ссылка,
Контрагенты.Наименование КАК Наименование
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ИнформационныеБазы КАК ИнформационныеБазы
ПО Контрагенты.Ссылка = ИнформационныеБазы.Владелец
ГДЕ
ИнформационныеБазы.Ссылка ЕСТЬ NULL
Важный момент: Проверять на NULL нужно именно поле Ссылка (или любое другое поле, которое гарантированно заполнено в подчиненной таблице). Если вы проверите какой-то реквизит, который сам по себе может быть не заполнен, вы получите неверный результат.
Если логика с соединениями кажется вам менее наглядной, можно воспользоваться конструкцией НЕ В. С точки зрения «человеческого» языка это звучит так: «Выбери всех контрагентов, чья ссылка не встречается в списке владельцев справочника информационных баз».
Давайте посмотрим на пример реализации:
ВЫБРАТЬ
Контрагенты.Ссылка
ИЗ
Справочник.Контрагенты КАК Контрагенты
ГДЕ
Контрагенты.Ссылка НЕ В
(ВЫБРАТЬ
ИнформационныеБазы.Владелец
ИЗ
Справочник.ИнформационныеБазы КАК ИнформационныеБазы)
Хотя этот вариант читается легче, следует учитывать вопросы производительности. В СУБД MS SQL Server оптимизаторы обычно хорошо справляются с обоими вариантами, но на старых версиях платформы или специфических СУБД ЛЕВОЕ СОЕДИНЕНИЕ часто отрабатывает быстрее, чем NOT IN, особенно если в подчиненной таблице миллионы записей.
Начинающие разработчики часто путают эти два понятия, что приводит к пустым результатам запроса. Выясним причину этой разницы:
Значение(Справочник.ИнформационныеБазы.ПустаяСсылка)), которое физически существует, но ни на что не указывает.При использовании ЛЕВОГО СОЕДИНЕНИЯ, если связь не сработала, система возвращает именно NULL. Сравнение ИнформационныеБазы.Ссылка = ЗНАЧЕНИЕ(Справочник.ИнформационныеБазы.ПустаяСсылка) в блоке ГДЕ не поможет найти контрагентов без баз, так как NULL не равен пустой ссылке.
Проанализируем ситуацию, когда у контрагента есть информационные базы, но все они помечены на удаление. Если бизнес-логика требует считать такого контрагента «пустым», нам нужно изменить условие соединения.
Внимание: Условие на отсутствие пометки удаления нужно ставить именно в блоке ПО (условие связи), а не в блоке ГДЕ. Рассмотрим почему:
ВЫБРАТЬ
Контрагенты.Ссылка
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ИнформационныеБазы КАК ИнформационныеБазы
ПО Контрагенты.Ссылка = ИнформационныеБазы.Владелец
И ИнформационныеБазы.ПометкаУдаления = ЛОЖЬ
ГДЕ
ИнформационныеБазы.Ссылка ЕСТЬ NULL
Если мы перенесем ИнформационныеБазы.ПометкаУдаления = ЛОЖЬ в ГДЕ, то запрос сначала соединит все записи, а потом отфильтрует те, где есть пометка. В итоге мы потеряем те строки, где ИнформационныеБазы.Ссылка ЕСТЬ NULL, так как NULL не может быть равен ЛОЖЬ. Указывая условие в ПО, мы заставляем систему соединять контрагента только с «живыми» базами. Если «живых» нет — получим NULL, что нам и нужно.
Если в вашей базе данных сотни тысяч контрагентов и столько же записей в подчиненном справочнике, прямое соединение может нагрузить сервер. В профессиональной разработке 1С рекомендуется использовать временные таблицы. Разберем этот подход по шагам:
ВТ_Владельцы.Контрагенты с ВТ_Владельцы левым соединением.Пример оптимизированного запроса:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ИнформационныеБазы.Владелец КАК Владелец
ПОМЕСТИТЬ ВТ_Владельцы
ИЗ
Справочник.ИнформационныеБазы КАК ИнформационныеБазы
ИНДЕКСИРОВАТЬ ПО
Владелец
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Контрагенты.Ссылка
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Владельцы КАК ВТ_Владельцы
ПО Контрагенты.Ссылка = ВТ_Владельцы.Владелец
ГДЕ
ВТ_Владельцы.Владелец ЕСТЬ NULL
Такой подход значительно ускоряет работу, так как СУБД будет сравнивать данные с предварительно подготовленным и сжатым списком, а не сканировать всю таблицу подчиненного справочника целиком при каждом сопоставлении.
Подводя итог, можно сказать, что решение проблемы «тупняка» при составлении подобных запросов заключается в четком понимании работы ЛЕВОГО СОЕДИНЕНИЯ. Помните о NULL, правильно работайте с условиями пометки удаления и не забывайте про временные таблицы в высоконагруженных системах.