Почему пустая ссылка не равна пустой ссылке в 1С: разбираемся с составными типами

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При разработке или переносе данных в 1С программисты часто сталкиваются с загадочной ситуацией: два поля в запросе выглядят абсолютно одинаково (как пустые ссылки), их GUID полностью состоят из нулей, но при сравнении оператор = внезапно возвращает Ложь. Особенно часто это проявляется в конфигурациях на базе 1С:ERP или УТ 11, где активно используются реквизиты составного типа. Проанализируем ситуацию, выясним причину и найдем правильные способы решения этой проблемы.

Суть проблемы: когда пустота бывает разной

Рассмотрим ситуацию на примере запроса к документам ввода остатков. Предположим, мы сравниваем договор из табличной части документа и договор из регистра сведений. Оба поля в консоли запросов отображаются как пустые значения. Выполним следующий запрос:


ВЫБРАТЬ 
    ТаблицаРасчетыСПартнерами.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка) КАК ЭтоПустойДоговор1,
    РегистрАналитикаУчетаПоПартнерам.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка) КАК ЭтоПустойДоговор2,
    ТаблицаРасчетыСПартнерами.Договор = РегистрАналитикаУчетаПоПартнерам.Договор КАК ОниРавны
ИЗ 
    Документ.ВводОстатковВзаиморасчетов.РасчетыСПартнерами КАК ТаблицаРасчетыСПартнерами
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.АналитикаУчетаПоПартнерам КАК РегистрАналитикаУчетаПоПартнерам
        ПО ТаблицаРасчетыСПартнерами.Партнер = РегистрАналитикаУчетаПоПартнерам.Партнер
ГДЕ Ссылка = &Ссылка

Проанализируем результат: если документ создан вручную, мы везде получим Истина. Однако, если документ создан программно, в колонке ОниРавны может появиться Ложь, хотя первые две колонки по-прежнему будут утверждать, что оба значения — это пустые ссылки Справочник.ДоговорыКонтрагентов.

Причина 1: Неопределено vs ПустаяСсылка

Главная особенность 1С заключается в том, что реквизит составного типа может принимать три принципиально разных состояния:

  1. Неопределено — когда тип значения вообще не выбран (например, поле в обработке или программно созданный реквизит документа, который не был инициализирован).
  2. Пустая ссылка конкретного типа — когда тип уже выбран (например, Справочник.ДоговорыКонтрагентов), но сам объект не указан.
  3. Заполненное значение — когда выбран и тип, и конкретный элемент.

В языке запросов 1С значение Неопределено при сравнении с пустой ссылкой любого типа всегда дает Ложь. При этом, если вы попытаетесь посмотреть УникальныйИдентификатор() в консоли запросов для обоих случаев, вы увидите одинаковую строку из нулей: 00000000-0000-0000-0000-000000000000. Это и сбивает разработчиков с толку.

Как проверить типы значений в запросе

Чтобы убедиться в разнице типов, добавим в наш запрос использование функции ТИПЗНАЧЕНИЯ. Рассмотрим обновленный пример:


ВЫБРАТЬ
    ТИПЗНАЧЕНИЯ(ТаблицаРасчетыСПартнерами.Договор) КАК Тип1,
    ТИПЗНАЧЕНИЯ(РегистрАналитикаУчетаПоПартнерам.Договор) КАК Тип2,
    ТаблицаРасчетыСПартнерами.Договор = РегистрАналитикаУчетаПоПартнерам.Договор КАК ОниРавны
ИЗ 
    Документ.ВводОстатковВзаиморасчетов.РасчетыСПартнерами КАК ТаблицаРасчетыСПартнерами

Если в поле Тип1 вы увидите Неопределено, а в поле Тип2 — Договор контрагента, то причина неравенства становится очевидной. Платформа видит, что типы не совпадают, и даже не переходит к сравнению идентификаторов.

Взгляд на проблему со стороны SQL

Если мы заглянем "под капот" СУБД (MS SQL или PostgreSQL), то увидим, что поле составного типа хранится в нескольких колонках. Основные из них — это _TYPE (хранит код типа) и _IDRRef (хранит GUID).

Для Неопределено в колонке типа будет стоять технический маркер отсутствия типа (например, 0x00), а в колонке ссылки — нули. Для Пустой ссылки в колонке типа будет записан реальный числовой идентификатор таблицы справочника договоров, а в ссылке — те же нули. С точки зрения SQL-сервера, записи (0x00, 0x00...) и (0x52, 0x00...) — это разные данные, поэтому они не равны.

Как правильно проверять "пустоту" в запросах

Рассмотрим несколько способов корректной проверки незаполненных значений составного типа, чтобы избежать ошибок логики.

Способ 1: Использование оператора ССЫЛКА

Это самый быстрый и "безопасный" способ отсечь лишние типы. Рассмотрим пример:


ГДЕ (НЕ РегистрАналитикаУчетаПоПартнерам.Договор ССЫЛКА Справочник.ДоговорыКонтрагентов
    ИЛИ РегистрАналитикаУчетаПоПартнерам.Договор = ЗНАЧЕНИЕ(Справочник.ДоговорыКонтрагентов.ПустаяСсылка))

Однако такой подход громоздок. Если нам нужно просто понять, заполнено ли поле хоть чем-то, лучше использовать другие методы.

Способ 2: Проверка на NULL через точку

Существует "хитрый" прием, который часто используют опытные разработчики. Если обратиться к реквизиту Ссылка через точку от составного поля, то для Неопределено результат всегда будет NULL.


ВЫБРАТЬ
    ПолеСоставногоТипа.Ссылка IS NULL КАК ЭтоПусто

Важное предостережение: использование обращения через точку .Ссылка от составного поля заставляет платформу генерировать LEFT JOIN со всеми таблицами, входящими в этот тип. Если в составной тип входит 20 справочников, ваш запрос станет крайне медленным. Используйте этот метод с осторожностью.

Способ 3: Приведение к конкретному типу (ВЫРАЗИТЬ)

Если мы заранее знаем, какой тип нас интересует, лучше всего использовать ВЫРАЗИТЬ. Посмотрим, как это работает:


ВЫБРАТЬ
    ВЫРАЗИТЬ(Таблица.Договор КАК Справочник.ДоговорыКонтрагентов) КАК Договор
ГДЕ
    ВЫРАЗИТЬ(Таблица.Договор КАК Справочник.ДоговорыКонтрагентов) IS NULL

В данном случае, если в поле было Неопределено или ссылка другого типа, функция вернет NULL, который легко проверить.

Как избежать проблемы при программном создании документов

Чтобы ваши запросы работали предсказуемо, необходимо правильно инициализировать реквизиты при программном заполнении. Рассмотрим пример кода, который гарантирует, что в поле будет именно пустая ссылка нужного типа, а не Неопределено:


НоваяСтрока = ОбъектДокумент.РасчетыСПартнерами.Добавить();
НоваяСтрока.Партнер = СсылкаНаПартнера;
// Вместо того чтобы оставить поле пустым, явно назначаем пустую ссылку типа
НоваяСтрока.Договор = Справочники.ДоговорыКонтрагентов.ПустаяСсылка();

Если тип реквизита заранее неизвестен (динамический алгоритм), можно воспользоваться метаданными для получения массива всех возможных пустых ссылок, но на практике достаточно инициализировать те поля, по которым в дальнейшем планируется соединение или сравнение в запросах.

Резюме

Разберем основные выводы нашего исследования:

  1. Пустая ссылка конкретного типа и Неопределено — это разные значения для 1С.
  2. В консоли запросов они могут выглядеть одинаково (нулевой GUID), что затрудняет отладку.
  3. Всегда используйте ТИПЗНАЧЕНИЯ() в запросе, если сомневаетесь в результатах сравнения.
  4. При программном создании объектов всегда инициализируйте реквизиты составного типа конкретной ПустойСсылкой(), если значение не задано.
  5. Для проверки "пустоты" в запросе эффективнее всего использовать ВЫРАЗИТЬ ... КАК ... IS NULL или оператор ССЫЛКА.
← На главную