При работе со справочниками в платформе 1С:Предприятие разработчики часто сталкиваются с необходимостью выбрать элементы самого верхнего уровня иерархии — то есть те элементы, у которых нет родителя (они находятся в корне справочника). На первый взгляд задача кажется тривиальной, однако попытка решить ее интуитивным путем часто приводит к логическим ошибкам в запросе и возврату пустой выборки.
В рамках этой статьи мы подробно разберем, почему стандартная проверка через обращение к реквизиту Родитель.Ссылка не работает, погрузимся во внутренние механизмы СУБД и платформы 1С, а также рассмотрим наиболее эффективные и оптимизированные способы решения этой задачи.
Рассмотрим типичную ошибку, которую совершают начинающие разработчики. В исходном запросе условие отбора элементов без родителя выглядело следующим образом:
ГДЕ
Контрагент.Родитель.Ссылка = &ПС
Где в качестве параметра &ПС передавалась пустая ссылка справочника. При выполнении такого запроса результат оказывается пустым, хотя в базе данных точно присутствуют элементы верхнего уровня с заполненной электронной почтой. Давайте разберем по шагам, почему так происходит.
Когда платформа 1С:Предприятие транслирует запрос из своего внутреннего языка (SDBL) в язык SQL, понятный СУБД (например, MS SQL Server или PostgreSQL), любое обращение через точку к ссылочному полю (так называемое разыменование ссылки) преобразуется в операцию левого соединения (LEFT JOIN). Таким образом, конструкция Контрагент.Родитель.Ссылка заставляет платформу связать таблицу справочника саму с собой по следующему условию:
LEFT JOIN Справочник.Контрагенты КАК РодительскиеЗаписи
ON Контрагент.Родитель = РодительскиеЗаписи.Ссылка
У элементов верхнего уровня в поле Родитель физически записан пустой уникальный идентификатор (UUID, состоящий из одних нулей: 00000000-0000-0000-0000-000000000000). При выполнении левого соединения СУБД пытается найти в этой же таблице запись, у которой поле Ссылка (первичный ключ) равно этому пустому UUID.
Однако в физических таблицах базы данных 1С записи с «нулевым» идентификатором не существует. Ссылка на пустой элемент — это системная виртуальная сущность платформы, но в самой таблице СУБД такой строки нет. В результате левое соединение не находит соответствия, и для всех полей присоединенной таблицы (включая поле Ссылка родителя) СУБД возвращает специальное маркерное значение NULL.
Таким образом, выражение в условии превращается в сравнение:
NULL = &ПС
В реляционных базах данных и языке SQL любое сравнение значения с NULL (будь то равенство или неравенство) всегда возвращает результат UNKNOWN (неизвестно), что в логике фильтрации секции ГДЕ приравнивается к значению ЛОЖЬ. Именно поэтому все корневые элементы справочника отсекаются, и запрос возвращает пустую выборку.
Чтобы получить элементы без родителя, нам нужно избежать разыменования ссылки и лишних соединений таблиц на уровне СУБД. Поле Родитель само по себе уже содержит идентификатор. Мы можем сравнивать его напрямую, не обращаясь к его свойствам через точку. Рассмотрим два корректных и наиболее эффективных варианта написания запроса.
Этот способ является наиболее предпочтительным и соответствует стандартам разработки фирмы 1С. Мы сравниваем поле Родитель напрямую с пустой ссылкой, используя предопределенную конструкцию языка запросов:
ВЫБРАТЬ
Контрагент.Ссылка КАК Дилер,
Контрагент.ЭлПочта КАК ЭлПочта
ИЗ
Справочник.Контрагенты КАК Контрагент
ГДЕ
Контрагент.ЭлПочта <> ""
И Контрагент.Родитель = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
УПОРЯДОЧИТЬ ПО
Контрагент.Наименование
Проанализируем преимущества данного подхода:
Родитель в иерархических справочниках 1С всегда индексируется платформой автоматически. Прямое сравнение позволяет СУБД эффективно использовать индекс (выполнить операцию Index Seek вместо полного сканирования таблицы Table Scan).УстановитьПараметр(). Конструкция ЗНАЧЕНИЕ(...) вычисляется платформой самостоятельно на этапе компиляции запроса.Если по каким-то причинам вам необходимо передавать пустую ссылку динамически из кода (например, при построении универсальных механизмов отчетов), вы можете использовать классические параметры запроса, но обязательно сравнивать их напрямую с полем Родитель:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагент.Ссылка КАК Дилер,
| Контрагент.ЭлПочта КАК ЭлПочта
|ИЗ
| Справочник.Контрагенты КАК Контрагент
|ГДЕ
| Контрагент.ЭлПочта <> """"
| И Контрагент.Родитель = &ПустаяСсылкаРодителя
|
|УПОРЯДОЧИТЬ ПО
| Контрагент.Наименование";
Запрос.УстановитьПараметр("ПустаяСсылкаРодителя", Справочники.Контрагенты.ПустаяСсылка());
РезультатЗапроса = Запрос.Выполнить();
Этот вариант также полностью работоспособен и быстр, так как не содержит разыменования через точку.
Давайте подробнее разберем влияние обращений через точку в запросах 1С на общую производительность системы. Разыменование ссылочных полей — это одна из ключевых причин замедления работы баз данных при росте объемов информации.
Каждое обращение к реквизиту через точку (например, Контрагент.Родитель.Код или Контрагент.ВалютаВзаиморасчетов.Код) неявно добавляет в SQL-запрос еще одно левое соединение. Если ваш запрос выбирает данные из крупной таблицы документов и при этом вы несколько раз обращаетесь к реквизитам контрагента, его договора или организации через точку, итоговый SQL-текст может содержать десятки соединений LEFT JOIN.
Для СУБД обработка запросов с большим количеством соединений становится тяжелой задачей. Оптимизатору запросов сложнее построить эффективный план выполнения, возрастает потребление оперативной памяти сервера и увеличивается время ожидания транзакций, что в конечном итоге может приводить к избыточным блокировкам базы данных.
Золотое правило разработчика 1С: Если вам нужно просто проверить заполненность ссылочного поля или сравнить его с пустой ссылкой, всегда делайте это напрямую без обращения к его свойствам.
Для успешного написания сложных запросов крайне важно четко понимать разницу между терминами ПустаяСсылка и NULL. Эти понятия часто путают, что ведет к ошибкам в логике.
Пустая ссылка — это вполне определенное, типизированное значение. Она относится к конкретному типу данных (например, СправочникСсылка.Контрагенты). Это объект, который существует в парадигме 1С и физически записывается в СУБД в виде шестнадцатеричной строки из нулей. Пустую ссылку можно сравнивать с другими ссылками с помощью стандартных операторов равенства (=, <>).
NULL — это отсутствие значения как такового. Оно не принадлежит ни к какому типу данных. NULL возникает только в процессе выполнения запросов при левых/правых соединениях (когда для записи из левой таблицы не нашлось соответствия в правой), при объединении таблиц или при использовании некоторых встроенных функций. Проверить значение на равенство NULL обычным оператором равенства (= NULL) невозможно — для этого в языке запросов 1С предусмотрен специальный оператор ЕСТЬ NULL.
Иногда перед разработчиком стоит задача проверить родителя более глубокого уровня — например, получить элементы, у которых «дедушка» (родитель родителя) является пустым. В этом случае коварство значений NULL проявляется еще сильнее.
Допустим, мы пишем следующее условие:
ГДЕ
Контрагент.Родитель.Родитель = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
Давайте проанализируем, как поведет себя это условие для элементов разного уровня иерархии:
Родитель вернет NULL. Соответственно, при попытке получить родителя от NULL СУБД вернет NULL. Условие превратится в NULL = ПустаяСсылка, и элемент не попадет в выборку.Чтобы избежать подобных проблем при работе с многоуровневой иерархией, необходимо использовать защитную функцию ЕСТЬNULL, которая заменяет неопределенное значение на заданное по умолчанию. Безопасный вариант проверки «дедушки» выглядит так:
ГДЕ
ЕСТЬNULL(Контрагент.Родитель.Родитель, ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)) = ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
Этот прием позволяет гарантировать, что даже при возникновении NULL на промежуточных этапах соединения таблиц, система корректно интерпретирует отсутствие родителя как пустую ссылку и вернет правильный результат.
Мы детально изучили проблему получения элементов справочника без родителя с помощью запросов 1С. Резюмируем основные выводы:
Родитель.Ссылка для проверки заполненности родителя. Это приводит к лишнему левому соединению, которое возвращает NULL для корневых элементов, делая выборку пустой.Родитель со значением пустой ссылки. Это работает максимально быстро и эффективно за счет использования индексов СУБД.ЗНАЧЕНИЕ(Справочник.ИмяСправочника.ПустаяСсылка) непосредственно в тексте запроса. Это делает код чище и помогает оптимизатору СУБД строить более качественные планы выполнения.NULL и типизированной пустой ссылкой, а также используйте функцию ЕСТЬNULL при работе со сложной многоуровневой иерархией.Применяя эти несложные правила оптимизации, вы сможете писать надежные, быстродействующие и легко читаемые запросы в любых прикладных решениях на платформе 1С:Предприятие.