Тип «Ссылка» во встроенном языке 1С:Предприятия 8.3: из чего состоит, как получить, сравнить и не замедлить программу

· 10 мин

Содержание

Ссылка в 1С — это значение, которое указывает на конкретный объект базы данных: элемент справочника, документ, счёт, значение перечисления. Внутри ссылка хранит всего две вещи: тип объекта и его уникальный идентификатор (UUID). Самих данных объекта в ней нет — за ними платформа идёт в базу, когда вы их запрашиваете.

Отсюда практические правила работы со ссылками в коде. Ссылку дёшево передавать между клиентом и сервером, хранить в реквизитах и коллекциях, сравнивать через =. А вот каждое обращение «через точку» к её реквизитам — это чтение из базы. Ниже разобраны виды ссылок, способы их получения, сравнение, преобразование в строку и ошибки, на которых чаще всего теряют производительность.

Из чего состоит ссылка и чем она отличается от объекта

Возьмём реквизит документа Контрагент. В таблице документа хранится не наименование и не ИНН контрагента, а 16 байт его идентификатора. Платформа знает, к какой таблице относится этот идентификатор, по типу реквизита. Если тип составной, рядом хранится ещё и номер таблицы.

Поэтому ссылка:

  • 🔑 неизменяема — нельзя «поменять» ссылку, можно только присвоить переменной другую;
  • 📦 лёгкая — передаётся на клиент и обратно без сериализации всего объекта;
  • 🔍 уникальна в пределах базы — два разных объекта не могут иметь одинаковую ссылку;
  • 🔒 только для чтения — изменить данные через ссылку нельзя, для записи нужен объект.

Сравните три близких по смыслу, но разных типа данных:

ТипПримерЧто содержитМожно ли записать
СсылкаСправочникСсылка.КонтрагентыТип + UUIDНет
ОбъектСправочникОбъект.КонтрагентыВсе реквизиты и табличные части в памятиДа, методом Записать()
ВыборкаСправочникВыборка.КонтрагентыТекущую запись при обходеНет, но можно получить объект

Переход от ссылки к объекту выполняется методом ПолучитьОбъект(). Обратно — через свойство Ссылка объекта:

// На сервере
Объект = Ссылка.ПолучитьОбъект();      // читаем объект целиком
Объект.Комментарий = "Проверено";
Объект.Записать();

НоваяСсылка = Объект.Ссылка;           // ссылка записанного объекта

У нового, ещё не записанного объекта свойство Ссылка содержит пустую ссылку. Настоящая ссылка появляется после первой записи. Если ссылка нужна раньше (например, чтобы связать два новых объекта), используйте УстановитьСсылкуНового() или ПолучитьСсылкуНового().

Какие бывают ссылочные типы в 1С:Предприятии

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

Объект конфигурацииСсылочный типПример получения
СправочникСправочникСсылка.<Имя>Справочники.Валюты.НайтиПоКоду("643")
ДокументДокументСсылка.<Имя>Документы.РеализацияТоваровУслуг.НайтиПоНомеру(...)
ПеречислениеПеречислениеСсылка.<Имя>Перечисления.ВидыОпераций...
План счетовПланСчетовСсылка.<Имя>ПланыСчетов.Хозрасчетный.НайтиПоКоду("60.01")
План видов характеристикПланВидовХарактеристикСсылка.<Имя>ПланыВидовХарактеристик.<Имя>.НайтиПоНаименованию(...)
План видов расчётаПланВидовРасчетаСсылка.<Имя>ПланыВидовРасчета.<Имя>.НайтиПоКоду(...)
План обменаПланОбменаСсылка.<Имя>ПланыОбмена.<Имя>.ЭтотУзел()
Бизнес-процессБизнесПроцессСсылка.<Имя>через запрос или выборку
ЗадачаЗадачаСсылка.<Имя>через запрос или выборку
Точка маршрутаТочкаМаршрутаБизнесПроцессаСсылка.<Имя>БизнесПроцессы.<Имя>.ТочкиМаршрута.<Точка>

Особняком стоят перечисления. Их значения задаются в конфигураторе, а не создаются пользователем. В коде к ним обращаются по имени: Перечисления.СтавкиНДС.НДС20. Записать значение перечисления как объект нельзя — у него нет метода ПолучитьОбъект().

Предопределённые элементы

Справочники, планы счетов и планы видов характеристик могут иметь предопределённые элементы. На них ссылаются по имени, а не по коду, — так код не ломается, если пользователь поменял код или наименование:

Счет = ПланыСчетов.Хозрасчетный.ОсновныеСредства;

На клиенте и в общих модулях, где менеджеры недоступны, используется функция ПредопределенноеЗначение():

Счет = ПредопределенноеЗначение("ПланСчетов.Хозрасчетный.ОсновныеСредства");
Ставка = ПредопределенноеЗначение("Перечисление.СтавкиНДС.НДС20");

Как получить ссылку в коде

Способов несколько. Выбор зависит от того, что вы знаете об объекте заранее.

Через методы менеджера

Менеджер объекта (Справочники.<Имя>, Документы.<Имя>) умеет искать:

  • 🔢 НайтиПоКоду(Код) — по коду справочника или плана счетов;
  • 🏷️ НайтиПоНаименованию(Наименование, ТочноеСоответствие) — по наименованию;
  • 🧾 НайтиПоРеквизиту(ИмяРеквизита, Значение) — по значению реквизита;
  • 📄 НайтиПоНомеру(Номер, ДатаИнтервала) — для документов.

Если ничего не найдено, метод возвращает пустую ссылку этого типа, а не Неопределено. Проверять результат нужно так:

Валюта = Справочники.Валюты.НайтиПоКоду("840");
Если Валюта.Пустая() Тогда
    ВызватьИсключение "Валюта с кодом 840 не найдена";
КонецЕсли;

НайтиПоНаименованию и НайтиПоРеквизиту возвращают первый найденный элемент. Если наименования повторяются (а в справочнике контрагентов так бывает часто), вы получите случайный из дублей. Для таких поисков надёжнее запрос с проверкой количества строк.

Через запрос

Запрос — основной способ получить ссылки по условию. Поле Ссылка есть у таблицы любого ссылочного объекта:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Контрагенты.Ссылка КАК Ссылка
|ИЗ
|    Справочник.Контрагенты КАК Контрагенты
|ГДЕ
|    Контрагенты.ИНН = &ИНН
|    И НЕ Контрагенты.ПометкаУдаления";
Запрос.УстановитьПараметр("ИНН", "7700000000");

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    Контрагент = Выборка.Ссылка;
КонецЕсли;

По уникальному идентификатору

Если UUID известен (пришёл из обмена, из внешней системы, из навигационной ссылки), ссылку собирают методом ПолучитьСсылку():

УИД = Новый УникальныйИдентификатор("4f2a1c3e-8b7d-11ee-a1b2-0050569f1a2b");
Ссылка = Справочники.Номенклатура.ПолучитьСсылку(УИД);

ПолучитьСсылку() не проверяет, существует ли объект в базе. Он просто собирает ссылку из типа и идентификатора. Чтобы убедиться, что объект есть, обратитесь к нему запросом или проверьте Ссылка.ПолучитьОбъект() <> Неопределено.

Пустая ссылка

У каждого ссылочного типа есть одно «пустое» значение. Его получают через менеджер или в запросе через ЗНАЧЕНИЕ():

Пустая = Справочники.Номенклатура.ПустаяСсылка();
ГДЕ Товары.Номенклатура = ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)

Сравнение ссылок и поиск в коллекциях

Ссылки сравниваются по значению. Две переменные равны, если у них совпадают тип и UUID. При этом неважно, как именно каждая ссылка была получена — из запроса, из реквизита или методом НайтиПоКоду().

Если Документ.Организация = ОсновнаяОрганизация Тогда
    // это одна и та же запись справочника
КонецЕсли;

Что нужно помнить при сравнении:

  • ⚖️ Ссылки разных типов никогда не равны, даже при одинаковом UUID.
  • 🚫 Операции < и > для ссылок не работают — платформа выдаст ошибку сравнения. Для упорядочивания сортируйте по реквизиту (наименованию, дате, коду).
  • 🕳️ Пустая ссылка не равна Неопределено. Реквизит составного типа может содержать и то и другое.
  • ✅ Для проверки «заполнено ли» используйте ЗначениеЗаполнено(Значение). Она вернёт Ложь и для пустой ссылки, и для Неопределено.
ПроверкаПустая ссылкаНеопределеноЗаполненная ссылка
Ссылка.Пустая()Истинаошибка: у значения нет методаЛожь
Значение = НеопределеноЛожьИстинаЛожь
ЗначениеЗаполнено(Значение)ЛожьЛожьИстина

Ссылки в массивах, соответствиях и таблицах

Раз ссылки сравниваются по значению, их удобно использовать в коллекциях:

// Проверка вхождения
Если МассивОрганизаций.Найти(Организация) <> Неопределено Тогда
    // организация уже есть в списке
КонецЕсли;

// Ссылка как ключ соответствия: накапливаем суммы по контрагентам
Итоги = Новый Соответствие;
Для Каждого Строка Из ТаблицаОплат Цикл
    Текущая = Итоги.Получить(Строка.Контрагент);
    Итоги.Вставить(Строка.Контрагент, ?(Текущая = Неопределено, 0, Текущая) + Строка.Сумма);
КонецЦикла;

В таблице значений поиск по ссылке выполняют методами Найти() и НайтиСтроки(). Если таблица большая и поиск идёт в цикле, добавьте индекс по колонке: Таблица.Индексы.Добавить("Контрагент").

Массив ссылок можно передать параметром в запрос и использовать в условии В (&Массив). Это быстрее, чем выполнять запрос по каждой ссылке отдельно.

Разыменование: обращение к реквизитам через точку

Запись Документ.Контрагент.ИНН выглядит невинно. Но на сервере платформа при этом читает данные контрагента из базы. Внутри одного вызова прочитанное кешируется, однако в цикле по тысяче документов с разными контрагентами вы получите тысячу обращений к базе.

Правила, которые снимают проблему:

  1. Нужные реквизиты связанных объектов получайте в том же запросе, что и основные данные: Реализация.Контрагент.ИНН КАК ИНН.
  2. Если нужен один-два реквизита одной ссылки, используйте функции БСП ОбщегоНазначения.ЗначениеРеквизитаОбъекта() и ОбщегоНазначения.ЗначенияРеквизитовОбъекта() — они читают только указанные поля одним запросом.
  3. Не вызывайте ПолучитьОбъект() ради чтения. Объект загружает все реквизиты и табличные части — это заметно тяжелее.

На тонком и веб-клиенте разыменование ссылок через точку недоступно вовсе. На клиенте ссылка — это только тип и идентификатор. Реквизиты получают серверным вызовом без контекста формы (&НаСервереБезКонтекста) или заранее кладут в реквизиты формы.

Разыменование в запросах и составные типы

В языке запросов точка тоже порождает соединение с таблицей. Если поле составного типа (например, Регистратор или Документ-основание), платформа соединит его со всеми возможными таблицами. Для регистра с десятком регистраторов это десяток соединений.

Решение — явно привести тип через ВЫРАЗИТЬ:

ВЫБРАТЬ
    ВЫРАЗИТЬ(Продажи.Регистратор КАК Документ.РеализацияТоваровУслуг).Номер КАК Номер
ИЗ
    РегистрНакопления.Продажи КАК Продажи
ГДЕ
    Продажи.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг

Оператор ССЫЛКА проверяет, относится ли значение составного поля к нужному типу. В коде на встроенном языке ту же задачу решает ТипЗнч(Значение) = Тип("ДокументСсылка.РеализацияТоваровУслуг").

Преобразование ссылки в строку и обратно

Ссылку часто нужно сохранить вне базы: передать в другую систему, записать в файл, вставить в письмо. Здесь легко ошибиться, потому что способов превратить ссылку в строку несколько, и они дают разный результат.

ФункцияЧто вернётМожно ли восстановить ссылку
Строка(Ссылка)Представление: наименование или «Реализация 00-000123 от 15.03…»Нет
XMLСтрока(Ссылка)UUID в виде 4f2a1c3e-8b7d-...Да, если тип известен
ЗначениеВСтрокуВнутр(Ссылка)Внутренний формат с типом и UUIDДа, ЗначениеИзСтрокиВнутр()
Ссылка.УникальныйИдентификатор()Значение типа УникальныйИдентификаторДа, через ПолучитьСсылку()
ПолучитьНавигационнуюСсылку(Ссылка)Строка вида e1cib/data/Справочник.Номенклатура?ref=...Да, через переход по ссылке в клиенте

Восстановление из UUID при известном типе:

СтрокаУИД = XMLСтрока(Номенклатура);   // сохранили

// ... позже, в другом месте
Номенклатура = XMLЗначение(Тип("СправочникСсылка.Номенклатура"), СтрокаУИД);

Не храните ссылки как результат Строка(). Наименования повторяются и меняются, а представление документа зависит от номера и даты. Через полгода найти по такой строке нужный объект не получится.

ЗначениеВСтрокуВнутр() подходит для обмена между базами одной конфигурации — например, для временного хранения в регистре сведений. Для обмена с внешними системами надёжнее пара «имя типа + UUID»: формат внутренней строки — служебный, и разбирать его вручную не стоит.

Навигационная ссылка удобна пользователю. Скопировать её можно из открытой формы объекта командой «Получить ссылку» (сочетание Shift+F11), а открыть — командой «Перейти по ссылке» из главного меню.

Битые ссылки и ссылочная целостность

Битая ссылка — значение, которое указывает на запись, которой в базе уже нет. В форме такое значение отображается как <Объект не найден> (…) с идентификатором в скобках. Откуда оно берётся:

  • 🗑️ объект удалили напрямую, без контроля ссылочной целостности;
  • 🔄 при обмене пришла ссылка на объект, который ещё не загружен;
  • 🧩 ссылку собрали через ПолучитьСсылку() из UUID, которого нет в базе;
  • 💾 база восстановлена частично, или таблицы правили внешними средствами.

В коде битую ссылку выдаёт ПолучитьОбъект(): для несуществующего объекта он возвращает Неопределено. Проверка Пустая() при этом вернёт Ложь — ведь UUID заполнен.

Объект = Ссылка.ПолучитьОбъект();
Если Объект = Неопределено Тогда
    // ссылка битая: объекта в базе нет
КонецЕсли;

Найти все битые ссылки в базе помогает режим тестирования и исправления в конфигураторе (Администрирование → Тестирование и исправление) с проверкой логической и ссылочной целостности. Перед запуском нужна резервная копия и монопольный доступ к базе — пользователи должны выйти. Выберите режим «Создавать объекты» или «Очищать ссылки» осознанно: первый создаёт пустые элементы-заглушки, второй стирает значения в реквизитах.

Чтобы битые ссылки не появлялись, удаляйте объекты через пометку удаления и обработку «Удаление помеченных объектов». Она проверяет, не используется ли объект где-то ещё, и не даст удалить то, на что ссылаются документы.

Где чаще всего ошибаются

Диагностика: выберите симптом

Ещё несколько ошибок, которые не дают сообщений, но портят результат:

  • 🔁 Сравнение с Неопределено вместо проверки заполненности. Пустая ссылка проходит такую проверку как «заполненная».
  • 🧮 Ссылки как строки в ключах. Соответствие с ключом Строка(Ссылка) склеит разных контрагентов с одинаковым наименованием.
  • 🧷 Кеширование объектов вместо ссылок. Объект в переменной модуля или в параметрах сеанса быстро устаревает, а ссылка — нет: данные по ней всегда читаются актуальные.

Частые вопросы

Можно ли изменить реквизит объекта через ссылку?
Нет. Ссылка только для чтения. Получите объект методом ПолучитьОбъект(), измените реквизит и вызовите Записать().
Сохраняется ли ссылка при выгрузке и загрузке базы в .dt?
Да. Выгрузка в .dt переносит идентификаторы как есть, поэтому все ссылки в восстановленной базе остаются прежними.
Чем пустая ссылка отличается от Неопределено?
Пустая ссылка имеет конкретный тип, например СправочникСсылка.Номенклатура, но не указывает на запись. Неопределено вообще не имеет типа. Реквизит составного типа может хранить оба значения.
Почему две базы одной конфигурации имеют разные ссылки на одинаковые элементы?
UUID генерируется при создании объекта в конкретной базе. Элемент «Рубль», созданный в двух базах независимо, получит два разных идентификатора. Совпадают только ссылки, перенесённые обменом или копированием базы.
Как узнать тип ссылки в коде?
Используйте ТипЗнч(Значение) и сравните с Тип(“ДокументСсылка.Имя”). Для имени объекта метаданных подойдёт Значение.Метаданные().ПолноеИмя().