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