В практике разработки на платформе 1С:Предприятие 8 часто возникает ситуация, когда при выборке данных из табличных частей документов или регистров некоторые ссылочные поля оказываются незаполненными. На уровне интерфейса мы видим пустую строку, однако для платформы и СУБД это поле содержит вполне конкретное значение — пустую ссылку соответствующего типа. Из-за этого запрос возвращает строки, которые визуально кажутся пустыми, и стандартные проверки на заполненность результата запроса не дают желаемого эффекта.
В рамках этой статьи мы подробно разберем, как правильно отсекать такие строки на уровне базы данных, рассмотрим внутренние механизмы СУБД, развеем популярные мифы о неявных соединениях и научимся заменять пустые ссылки на Неопределено или NULL в результатах выборки.
Давайте проанализируем исходную ситуацию. У нас есть запрос к табличной части «Товары» документа «Поступление товаров и услуг»:
ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
ПоступлениеТоваровУслугТовары.ЗаказПоставщику КАК ЗаказПоставщику
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ГДЕ
ПоступлениеТоваровУслугТовары.Ссылка = &СсылкаНаОбъект
Если в документе-источнике реквизит ЗаказПоставщику не заполнен, СУБД все равно возвращает запись. Дело в том, что в базе данных 1С пустая ссылка — это не пустота (не NULL) и не отсутствие типа. Это физическая запись, состоящая из уникального идентификатора (UUID/GUID), заполненного нулями (00000000-0000-0000-0000-000000000000). С точки зрения базы данных, это валидное значение, поэтому строка попадает в выборку.
Самый простой и производительный способ избавиться от таких строк — добавить в секцию ГДЕ проверку на равенство пустой ссылке конкретного типа документа. Рассмотрим, как это выглядит на практике:
ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
ПоступлениеТоваровУслугТовары.ЗаказПоставщику КАК ЗаказПоставщику
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ГДЕ
ПоступлениеТоваровУслугТовары.Ссылка = &СсылкаНаОбъект
И ПоступлениеТоваровУслугТовары.ЗаказПоставщику <> ЗНАЧЕНИЕ(Документ.ЗаказПоставщику.ПустаяСсылка)
Использование конструкции ЗНАЧЕНИЕ(...) крайне важно. Это предопределенная функция языка запросов 1С, которая вычисляется еще на этапе компиляции запроса самой платформой. В СУБД передается уже готовый двоичный идентификатор пустой ссылки, что позволяет СУБД выполнить запрос максимально быстро, используя существующие индексы. Для интерактивной проверки и отладки подобных запросов можно использовать консоль запросов для управляемых форм.
В процессе обсуждения подобных задач среди разработчиков часто возникает спор о «неявных левых соединениях» (Implicit Left Joins). Давайте выясним истину и разберем этот механизм детально.
Существует ошибочное мнение, что любое обращение к ссылочному реквизиту (например, ПоступлениеТоваровУслугТовары.ЗаказПоставщику) заставляет платформу выполнять неявное левое соединение с таблицей этого документа. Это не так.
Рассмотрим два сценария обращения к данным:
ПоступлениеТоваровУслугТовары.ЗаказПоставщику, платформа просто считывает шестнадцатеричный идентификатор (UUID) из физического поля таблицы табличной части. Никаких обращений к таблице «Заказ поставщику» не происходит. Запрос выполняется быстро, в рамках одной таблицы.ПоступлениеТоваровУслугТовары.ЗаказПоставщику.Дата, СУБД была бы вынуждена выполнить операцию LEFT JOIN с таблицей документов «Заказ поставщику», чтобы получить дату конкретного заказа. Вот это и называется неявным левым соединением, которое при неаккуратном использовании может существенно замедлить работу базы данных.Таким образом, простое сравнение реквизита с пустой ссылкой в секции ГДЕ не порождает никаких скрытых соединений и является абсолютно безопасным с точки зрения производительности.
Иногда разработчики советуют вместо условия ГДЕ использовать внутреннее соединение исходной таблицы с таблицей заказов. Давайте разберем по шагам, почему этот подход неоптимален для нашей задачи:
// НЕОПТИМАЛЬНЫЙ ВАРИАНТ!
ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ
ПоступлениеТоваровУслугТовары.ЗаказПоставщику КАК ЗаказПоставщику
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказПоставщику КАК ЗаказПоставщику
ПО ПоступлениеТоваровУслугТовары.ЗаказПоставщику = ЗаказПоставщику.Ссылка
ГДЕ
ПоступлениеТоваровУслугТовары.Ссылка = &СсылкаНаОбъект
При таком подходе СУБД вынуждена сканировать индекс таблицы «Заказы поставщикам» и сопоставлять данные. Если таблица заказов содержит сотни тысяч записей, это приведет к избыточному потреблению оперативной памяти и нагрузке на дисковую подсистему сервера. Использование простого условия ГДЕ ... <> ЗНАЧЕНИЕ(...) работает локально в рамках одной таблицы и всегда будет в разы быстрее.
Иногда бизнес-логика требует не просто отфильтровать строки, а вернуть их, заменив пустые ссылки на полноценные маркеры отсутствия данных — Неопределено или NULL. Это особенно полезно при последующем объединении таблиц или выводе данных в отчеты СКД.
Для решения этой задачи мы воспользуемся условной конструкцией ВЫБОР... КОГДА... ТОГДА... ИНАЧЕ... КОНЕЦ.
Этот вариант чаще всего применяется для реквизитов, которые в дальнейшем будут обрабатываться в коде на встроенном языке 1С, либо для полей составного типа данных, где тип еще не определен.
ВЫБРАТЬ
ВЫБОР
КОГДА ПоступлениеТоваровУслугТовары.ЗаказПоставщику = ЗНАЧЕНИЕ(Документ.ЗаказПоставщику.ПустаяСсылка)
ТОГДА НЕОПРЕДЕЛЕНО
ИНАЧЕ ПоступлениеТоваровУслугТовары.ЗаказПоставщику
КОНЕЦ КАК ЗаказПоставщику
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ГДЕ
ПоступлениеТоваровУслугТовары.Ссылка = &СсылкаНаОбъект
Этот вариант незаменим, если результат запроса планируется использовать во временных таблицах для последующих левых соединений (связей), так как СУБД оперирует понятием NULL на уровне ядра.
ВЫБРАТЬ
ВЫБОР
КОГДА ПоступлениеТоваровУслугТовары.ЗаказПоставщику = ЗНАЧЕНИЕ(Документ.ЗаказПоставщику.ПустаяСсылка)
ТОГДА NULL
ИНАЧЕ ПоступлениеТоваровУслугТовары.ЗаказПоставщику
КОНЕЦ КАК ЗаказПоставщику
ИЗ
Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
ГДЕ
ПоступлениеТоваровУслугТовары.Ссылка = &СсылкаНаОбъект
В типовых конфигурациях реквизиты вроде «ДокументОснование» или «Заказ» очень часто имеют составной тип данных (например, поле может содержать ссылку на Документ.ЗаказПоставщику, Документ.ЗаказКлиента или Документ.СчетНаОплату). Если мы применим обычную проверку на пустую ссылку одного конкретного документа, система пропустит пустые ссылки других типов.
Для корректной и быстрой работы с составными типами рекомендуется использовать два основных подхода:
Если в данном контексте нас интересует только конкретный тип документа из всех возможных в составе реквизита, мы принудительно ограничиваем его. Это исключает лишние проверки в СУБД:
ГДЕ
ВЫРАЗИТЬ(ПоступлениеТоваровУслугТовары.ЗаказПоставщику КАК Документ.ЗаказПоставщику) <> ЗНАЧЕНИЕ(Документ.ЗаказПоставщику.ПустаяСсылка)
Если поле может содержать пустую ссылку любого типа или значение Неопределено, мы можем использовать обращение к предопределенному свойству Ссылка. Если ссылка пустая или «битая» (указывает на несуществующий в базе объект), обращение к свойству вернет базу NULL:
ГДЕ
НЕ ПоступлениеТоваровУслугТовары.ЗаказПоставщику.Ссылка ЕСТЬ NULL
Важное предупреждение: Использование конструкции .Ссылка ЕСТЬ NULL для составного типа приводит к тому, что платформа 1С генерирует SQL-запрос с неявным левым соединением (LEFT JOIN) со всеми таблицами, входящими в этот составной тип. Если типов в составе реквизита очень много (например, более 10-15), это может критически замедлить выполнение запроса. Поэтому всегда отдавайте предпочтение конструкции ВЫРАЗИТЬ.