При работе с механизмами дополнительных свойств и категорий в конфигурациях на платформе 1С:Предприятие (например, в «Управление торговлей 10.3», «Управление производственным предприятием» или современных ERP-системах) разработчики часто сталкиваются с необходимостью вывести свойства объектов в одном отчете с основными данными. Для этого традиционно используется левое соединение. Однако нередко возникает ситуация, когда после написания запроса документы или справочники, у которых нужное свойство не заполнено, просто исчезают из результирующей таблицы. Получается, что левое соединение начинает отрабатывать как внутреннее.
В этой статье мы подробно разберем, почему возникает эта классическая ошибка, детально проанализируем поведение СУБД при выполнении подобных запросов, а также рассмотрим несколько способов решения этой проблемы: от простого исправления условий связи до глубокой оптимизации запроса и использования встроенных механизмов СКД.
Представим типовую задачу. В конфигурации «Управление торговлей 10.3» для документов реализации добавлено новое свойство «Водитель». Оно заполняется не для каждого документа. Нам требуется получить таблицу всех оборотов продаж с указанием водителя, если он задан. Если водитель не указан, документ все равно должен остаться в списке, а в поле водителя должно отобразиться пустое значение.
Проанализируем исходный запрос, который приводит к ошибке:
ВЫБРАТЬ
ПродажиОбороты.СтоимостьОборот,
ЗначенияСвойствОбъектов.Значение КАК Водитель,
ПродажиОбороты.ДокументПродажи
ИЗ
РегистрНакопления.Продажи.Обороты(&Дата1, &Дата2, Авто, ) КАК ПродажиОбороты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ПО ПродажиОбороты.ДокументПродажи = ЗначенияСвойствОбъектов.Объект
ГДЕ
ЗначенияСвойствОбъектов.Свойство = &СвойствоВодитель
Давайте разберем по шагам, как этот запрос обрабатывается СУБД:
ЗначенияСвойствОбъектов по условию равенства ссылки документа и измерения Объект. На этом шаге все работает верно. Для документов, у которых в регистре нет записи со свойством, создается строка, где все поля правой таблицы (включая Свойство и Значение) принимают значение NULL.ГДЕ. Здесь написано жесткое условие: ЗначенияСвойствОбъектов.Свойство = &СвойствоВодитель. Система начинает проверять каждую строчку результирующего набора данных. Если у документа не было задано ни одного свойства, то в поле Свойство находится значение NULL. СУБД вычисляет выражение NULL = &СвойствоВодитель. По правилам булевой логики баз данных, любое сравнение с NULL дает результат «Ложь» (или Unknown). В итоге все строки, где свойство не было заполнено, безжалостно отсекаются фильтром.Таким образом, наложение фильтра на правую (присоединяемую) таблицу в секции ГДЕ фактически превращает левое соединение во внутреннее (INNER JOIN). Это одна из самых распространенных логических ошибок начинающих разработчиков 1С.
Самый простой и быстрый способ исправить эту ошибку — перенести условие фильтрации правой таблицы непосредственно в условия соединения (секция ПО). Давайте посмотрим на правильный вариант запроса:
ВЫБРАТЬ
ПродажиОбороты.СтоимостьОборот,
ЗначенияСвойствОбъектов.Значение КАК Водитель,
ПродажиОбороты.ДокументПродажи
ИЗ
РегистрНакопления.Продажи.Обороты(&Дата1, &Дата2, Авто, ) КАК ПродажиОбороты
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ПО ПродажиОбороты.ДокументПродажи = ЗначенияСвойствОбъектов.Объект
И ЗначенияСвойствОбъектов.Свойство = &СвойствоВодитель
В этом случае СУБД сначала отфильтрует записи в таблице ЗначенияСвойствОбъектов, выбрав только те, которые относятся к свойству «Водитель», а уже затем выполнит левое соединение с продажами. Те документы, у которых данное свойство не заполнено, гарантированно останутся в выборке, а в поле Водитель для них вернется чистое значение NULL.
Хотя решение с переносом условия в секцию ПО является абсолютно рабочим, оно может работать неоптимально на крупных информационных базах. Регистр сведений ЗначенияСвойствОбъектов в реальных базах «Управление торговлей 10.3» часто разрастается до миллионов записей, так как хранит свойства для самых разных объектов: номенклатуры, контрагентов, документов, договоров.
При прямом левом соединении СУБД может выбрать неэффективный план запроса. Чтобы помочь оптимизатору СУБД построить быстрый план и избежать лишней нагрузки на сервер, мы рекомендуем предварительно выбирать нужные свойства во временную таблицу. Разберем этот подход по шагам:
// Шаг 1: Выбираем только свойства нужного типа во временную таблицу
ВЫБРАТЬ
ЗначенияСвойствОбъектов.Объект КАК Объект,
ЗначенияСвойствОбъектов.Значение КАК Водитель
ПОМЕСТИТЬ ВТ_ВодителиПоДокументам
ИЗ
РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ГДЕ
ЗначенияСвойствОбъектов.Свойство = &СвойствоВодитель
ИНДЕКСИРОВАТЬ ПО
Объект
;
// Шаг 2: Соединяем продажи со значительно уменьшенной временной таблицей
ВЫБРАТЬ
ПродажиОбороты.СтоимостьОборот,
ЕСТЬNULL(ВТ_ВодителиПоДокументам.Водитель, ЗНАЧЕНИЕ(Справочник.ЗначенияСвойствОбъектов.ПустаяСсылка)) КАК Водитель,
ПродажиОбороты.ДокументПродажи
ИЗ
РегистрНакопления.Продажи.Обороты(&Дата1, &Дата2, Авто, ) КАК ПродажиОбороты
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ВодителиПоДокументам КАК ВТ_ВодителиПоДокументам
ПО ПродажиОбороты.ДокументПродажи = ВТ_ВодителиПоДокументам.Объект
Давайте проанализируем преимущества этого подхода:
ВТ_ВодителиПоДокументам, которая содержит только те записи, у которых свойство равно «Водитель». Мы отсекаем весь гигантский объем лишних свойств других объектов.ИНДЕКСИРОВАТЬ ПО Объект создает временный индекс. При последующем левом соединении СУБД мгновенно найдет соответствия по индексу, что обеспечит максимальную скорость работы даже на базах объемом в терабайты.ЕСТЬNULL. Если у документа нет водителя, вместо невыразительного значения NULL в результат попадет стандартная пустая ссылка справочника значений свойств, что исключит проблемы с отображением пустых полей в отчетах или формах.Если ваша задача — построить отчет с помощью Системы компоновки данных (СКД), то вам вообще не обязательно вручную прописывать левые соединения с регистром сведений. В платформе 1С реализован мощный механизм под названием Характеристики.
Рассмотрим, как настроить его использование:
ДокументПродажи (или другой объект, свойства которого нужно получить).СвойстваОбъектов).ЗначенияСвойствОбъектов.После такой настройки в списке доступных полей СКД у поля ДокументПродажи появится виртуальный плюс, раскрыв который, вы увидите все дополнительные свойства (включая «Водитель») так, будто они являются реальными реквизитами самого документа. Вы можете просто перетащить поле ДокументПродажи.[Водитель] в выбранные поля отчета. Платформа СКД самостоятельно сформирует оптимальный SQL-запрос с правильным левым соединением и корректной обработкой пустых значений.
При работе с регистром ЗначенияСвойствОбъектов полезно помнить еще о нескольких важных моментах:
Значение в регистре сведений обычно имеет составной тип данных (это могут быть числа, строки, даты или ссылки на различные справочники). При левом соединении СУБД приходится работать со сложными типами данных. Если вы точно знаете, что значением свойства «Водитель» всегда является ссылка на справочник «Физические лица» или специальный справочник значений свойств, хорошей практикой считается явное приведение типов с помощью функции ВЫРАЗИТЬ(ЗначенияСвойствОбъектов.Значение КАК Справочник.ФизическиеЛица). Это повышает производительность запроса.Используйте описанные методики, избегайте фильтрации по присоединяемым таблицам в секции ГДЕ, и ваши запросы в 1С всегда будут работать быстро и корректно!