При разработке или переносе данных в 1С программисты часто сталкиваются с загадочной ситуацией: два поля в запросе выглядят абсолютно одинаково (как пустые ссылки), их GUID полностью состоят из нулей, но при сравнении оператор = внезапно возвращает Ложь. Особенно часто это проявляется в конфигурациях на базе 1С:ERP или УТ 11, где активно используются реквизиты составного типа. Проанализируем ситуацию, выясним причину и найдем правильные способы решения этой проблемы.
Рассмотрим ситуацию на примере запроса к документам ввода остатков. Предположим, мы сравниваем договор из табличной части документа и договор из регистра сведений. Оба поля в консоли запросов отображаются как пустые значения. Выполним следующий запрос:
ВЫБРАТЬ
ТаблицаРасчетыСПартнерами.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка) КАК ЭтоПустойДоговор1,
РегистрАналитикаУчетаПоПартнерам.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка) КАК ЭтоПустойДоговор2,
ТаблицаРасчетыСПартнерами.Договор = РегистрАналитикаУчетаПоПартнерам.Договор КАК ОниРавны
ИЗ
Документ.ВводОстатковВзаиморасчетов.РасчетыСПартнерами КАК ТаблицаРасчетыСПартнерами
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.АналитикаУчетаПоПартнерам КАК РегистрАналитикаУчетаПоПартнерам
ПО ТаблицаРасчетыСПартнерами.Партнер = РегистрАналитикаУчетаПоПартнерам.Партнер
ГДЕ Ссылка = &Ссылка
Проанализируем результат: если документ создан вручную, мы везде получим Истина. Однако, если документ создан программно, в колонке ОниРавны может появиться Ложь, хотя первые две колонки по-прежнему будут утверждать, что оба значения — это пустые ссылки Справочник.ДоговорыКонтрагентов.
Главная особенность 1С заключается в том, что реквизит составного типа может принимать три принципиально разных состояния:
Справочник.ДоговорыКонтрагентов), но сам объект не указан.В языке запросов 1С значение Неопределено при сравнении с пустой ссылкой любого типа всегда дает Ложь. При этом, если вы попытаетесь посмотреть УникальныйИдентификатор() в консоли запросов для обоих случаев, вы увидите одинаковую строку из нулей: 00000000-0000-0000-0000-000000000000. Это и сбивает разработчиков с толку.
Чтобы убедиться в разнице типов, добавим в наш запрос использование функции ТИПЗНАЧЕНИЯ. Рассмотрим обновленный пример:
ВЫБРАТЬ
ТИПЗНАЧЕНИЯ(ТаблицаРасчетыСПартнерами.Договор) КАК Тип1,
ТИПЗНАЧЕНИЯ(РегистрАналитикаУчетаПоПартнерам.Договор) КАК Тип2,
ТаблицаРасчетыСПартнерами.Договор = РегистрАналитикаУчетаПоПартнерам.Договор КАК ОниРавны
ИЗ
Документ.ВводОстатковВзаиморасчетов.РасчетыСПартнерами КАК ТаблицаРасчетыСПартнерами
Если в поле Тип1 вы увидите Неопределено, а в поле Тип2 — Договор контрагента, то причина неравенства становится очевидной. Платформа видит, что типы не совпадают, и даже не переходит к сравнению идентификаторов.
Если мы заглянем "под капот" СУБД (MS SQL или PostgreSQL), то увидим, что поле составного типа хранится в нескольких колонках. Основные из них — это _TYPE (хранит код типа) и _IDRRef (хранит GUID).
Для Неопределено в колонке типа будет стоять технический маркер отсутствия типа (например, 0x00), а в колонке ссылки — нули. Для Пустой ссылки в колонке типа будет записан реальный числовой идентификатор таблицы справочника договоров, а в ссылке — те же нули. С точки зрения SQL-сервера, записи (0x00, 0x00...) и (0x52, 0x00...) — это разные данные, поэтому они не равны.
Рассмотрим несколько способов корректной проверки незаполненных значений составного типа, чтобы избежать ошибок логики.
Это самый быстрый и "безопасный" способ отсечь лишние типы. Рассмотрим пример:
ГДЕ (НЕ РегистрАналитикаУчетаПоПартнерам.Договор ССЫЛКА Справочник.ДоговорыКонтрагентов
ИЛИ РегистрАналитикаУчетаПоПартнерам.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка))
Однако такой подход громоздок. Если нам нужно просто понять, заполнено ли поле хоть чем-то, лучше использовать другие методы.
Существует "хитрый" прием, который часто используют опытные разработчики. Если обратиться к реквизиту Ссылка через точку от составного поля, то для Неопределено результат всегда будет NULL.
ВЫБРАТЬ
ПолеСоставногоТипа.Ссылка IS NULL КАК ЭтоПусто
Важное предостережение: использование обращения через точку .Ссылка от составного поля заставляет платформу генерировать LEFT JOIN со всеми таблицами, входящими в этот тип. Если в составной тип входит 20 справочников, ваш запрос станет крайне медленным. Используйте этот метод с осторожностью.
Если мы заранее знаем, какой тип нас интересует, лучше всего использовать ВЫРАЗИТЬ. Посмотрим, как это работает:
ВЫБРАТЬ
ВЫРАЗИТЬ(Таблица.Договор КАК Справочник.ДоговорыКонтрагентов) КАК Договор
ГДЕ
ВЫРАЗИТЬ(Таблица.Договор КАК Справочник.ДоговорыКонтрагентов) IS NULL
В данном случае, если в поле было Неопределено или ссылка другого типа, функция вернет NULL, который легко проверить.
Чтобы ваши запросы работали предсказуемо, необходимо правильно инициализировать реквизиты при программном заполнении. Рассмотрим пример кода, который гарантирует, что в поле будет именно пустая ссылка нужного типа, а не Неопределено:
НоваяСтрока = ОбъектДокумент.РасчетыСПартнерами.Добавить();
НоваяСтрока.Партнер = СсылкаНаПартнера;
// Вместо того чтобы оставить поле пустым, явно назначаем пустую ссылку типа
НоваяСтрока.Договор = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();
Если тип реквизита заранее неизвестен (динамический алгоритм), можно воспользоваться метаданными для получения массива всех возможных пустых ссылок, но на практике достаточно инициализировать те поля, по которым в дальнейшем планируется соединение или сравнение в запросах.
Разберем основные выводы нашего исследования:
Неопределено — это разные значения для 1С.ТИПЗНАЧЕНИЯ() в запросе, если сомневаетесь в результатах сравнения.ПустойСсылкой(), если значение не задано.ВЫРАЗИТЬ ... КАК ... IS NULL или оператор ССЫЛКА.