Битая ссылка или пустая выборка: диагностика запросов в 1С 8.3

· 9 мин

Содержание

Чтобы проверить, что объект не найден, в запросе 1С соединяют поле-ссылку с таблицей объекта и отбирают строки, где Таблица.Ссылка ЕСТЬ NULL. Пустой результат запроса проверяют методом РезультатЗапроса.Пустой(), а существование одной ссылки — запросом ВЫБРАТЬ ПЕРВЫЕ 1 по полю Ссылка. Функция ЗначениеЗаполнено() для такой проверки не годится: битая ссылка для неё «заполнена».

Это две разные ситуации, и их часто путают. Пустая выборка — запрос отработал, но условия не нашли строк. «Объект не найден» — в поле лежит ссылка на запись, которой в базе нет. Лечатся они по-разному, поэтому сначала разберитесь, какая из них у вас.

Откуда берутся пустая выборка и «Объект не найден»

Ссылка в 1С хранит тип и уникальный идентификатор объекта. Если запись с таким идентификатором удалили или не загрузили, в форме вместо значения появляется строка вида <Объект не найден> (45:b2c4...). Число до двоеточия — внутренний номер таблицы, дальше — идентификатор во внутреннем представлении.

🔍 Типичные причины битых ссылок:

  • 🗑️ объект удалили напрямую методом Удалить() — без контроля ссылок, в обход «Удаления помеченных объектов»;
  • 🔄 при обмене данными пришёл документ, а справочник, на который он ссылается, не выгружен или ещё не загружен;
  • 📦 загрузили часть данных через XML или внешнюю обработку , где записи шли с режимом ОбменДанными.Загрузка = Истина;
  • 💾 восстановили базу или отдельные таблицы из разных копий;
  • 🧩 удалили объект метаданных или изменили тип реквизита, а старые данные остались.

🔍 Причины пустой выборки при живых данных:

  • ❌ параметр передан не того типа: строка вместо ссылки, дата без времени;
  • ❌ условие на поле присоединённой таблицы стоит в ГДЕ, а не в ПО;
  • ❌ сравнение с NULL через = вместо ЕСТЬ NULL;
  • ❌ у пользователя ограничен доступ к записям, а запрос написан с РАЗРЕШЕННЫЕ.
Диагностика: выберите симптом

Как проверить в коде, что запрос ничего не вернул

Самый быстрый способ — спросить у результата, есть ли в нём строки. Выгружать его в таблицу ради этого не нужно.

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

РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
	Сообщить("Контрагент с ИНН " + ИНН + " не найден");
	Возврат;
КонецЕсли;

Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
	// обработка найденных строк
КонецЦикла;

Если строк может быть несколько, а нужна одна, используйте Если Выборка.Следующий() Тогда … Иначе … КонецЕсли. Метод Следующий() вернёт Ложь на пустой выборке, и ветка Иначе обработает случай «не найдено».

Сравнение способов:

СпособКогда применятьПодвох
РезультатЗапроса.Пустой()Нужно только знать, есть ли строкиНе говорит, сколько строк
Выборка.Следующий() в ЕслиНужна первая строка или «не найдено»Остальные строки не читаются
Выгрузить().Количество() = 0Таблица всё равно нужна дальшеЛишняя память, если таблица не нужна
НайтиПоКоду(), НайтиПоНаименованию()Поиск одного элемента справочникаВозвращают пустую ссылку, проверяйте .Пустая()

У НайтиПоНаименованию() второй параметр — точное соответствие. Без него поиск идёт по началу строки, и на «ООО Ромашка» найдётся «ООО Ромашка-Плюс».

Параметры и условия отбора: где теряются строки

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

Тип и значение параметра

  1. Поставьте точку останова на строке Запрос.Выполнить().
  2. В отладчике раскройте Запрос.Параметры и проверьте тип каждого значения.
  3. Частая ошибка — в параметр ушло представление ссылки (строка), а сравнивается поле ссылочного типа.
  4. Если параметр не установлен вовсе, запрос завершится ошибкой, а не пустым результатом. Это хотя бы видно сразу.

Границы периода

Дата 31.12 без времени — это 31.12 0:00:00. Документы, проведённые днём 31 декабря, в выборку <= &КонецПериода не попадут. Передавайте КонецДня(Дата) или пишите в тексте запроса КОНЕЦПЕРИОДА(&КонецПериода, ДЕНЬ).

Условия на присоединённые таблицы

Условие в ГДЕ на поле таблицы, присоединённой через ЛЕВОЕ СОЕДИНЕНИЕ, превращает соединение во внутреннее. Строки, для которых справа ничего не нашлось, отсекаются.

// Плохо: потеряются контрагенты без договоров
ГДЕ Договоры.ВидДоговора = &Вид

// Хорошо: условие в соединении
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
ПО Договоры.Владелец = Контрагенты.Ссылка
	И Договоры.ВидДоговора = &Вид

Виртуальные таблицы регистров

Отбор для Остатки и ОстаткиИОбороты пишите в параметрах виртуальной таблицы, а не в ГДЕ. Результат может совпасть, но в параметрах платформа фильтрует данные до расчёта, и запрос работает быстрее.

Сравнение с NULL

Поле = NULL никогда не даёт истину. Для проверки отсутствия значения нужно Поле ЕСТЬ NULL, а чтобы подставить значение по умолчанию — ЕСТЬNULL(Поле, 0).

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

Проверка «объект не найден» в запросе строится на соединении. Для битой ссылки справа ничего не находится, и поле Ссылка правой таблицы получает NULL.

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

Второе условие отсекает пустые поля: пустая ссылка тоже не найдётся в справочнике, но битой она не считается. Если поле составного типа, добавьте ВЫРАЗИТЬ или проверку ТИПЗНАЧЕНИЯ(), чтобы отбирать ссылки только нужного типа.

Проверка одной ссылки

Для одной ссылки достаточно короткого запроса по индексу:

Функция СсылкаСуществует(Ссылка)
	Если Не ЗначениеЗаполнено(Ссылка) Тогда
		Возврат Ложь;
	КонецЕсли;
	Запрос = Новый Запрос;
	Запрос.Текст = СтрШаблон(
		"ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА ИЗ %1 ГДЕ Ссылка = &Ссылка",
		Ссылка.Метаданные().ПолноеИмя());
	Запрос.УстановитьПараметр("Ссылка", Ссылка);
	Возврат Не Запрос.Выполнить().Пустой();
КонецФункции

В типовых конфигурациях на БСП похожая функция уже есть — ОбщегоНазначения.СсылкаСуществует(). Своя нужна, если БСП в конфигурации нет.

Ссылка.ПолучитьОбъект() на битой ссылке возвращает Неопределено. Это тоже проверка, но она читает объект целиком вместе с табличными частями. Для массовой проверки берите запрос.

Что делать с найденными ссылками

ВариантКогда подходитРиск
Восстановить объект с той же ссылкойОбъект нужен, например контрагент в проведённых документахНужно заполнить реквизиты вручную
Заменить на правильный объектЕсть дубль или верный элементПерепроведение документов меняет движения
Очистить полеЗначение не нужноДокумент может перестать проводиться
Догрузить данные обменомСсылки пришли из другой базыНужно наладить сам обмен

Восстановить объект с той же ссылкой можно кодом. Тогда все документы снова увидят своего контрагента:

Объект = Справочники.Контрагенты.СоздатьЭлемент();
Объект.УстановитьСсылкуНового(БитаяСсылка);
Объект.Наименование = "Восстановлен: уточнить";
Объект.Записать();

Проверка через конфигуратор

Для всей базы сразу есть Администрирование → Тестирование и исправление в конфигураторе. Включите проверку ссылочной целостности и выберите, что делать при ссылках на несуществующие объекты: создавать объекты, очищать ссылки или не изменять.

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

Вариант «создавать объекты» создаёт пустые элементы-заглушки. После него пройдитесь по ним и заполните реквизиты, иначе в отчётах появятся безымянные строки.

Права доступа: почему у пользователя пусто, а у администратора нет

Запрос выполняется с правами текущего пользователя. Если в роли настроено ограничение доступа на уровне записей (RLS), результат зависит от того, как написан запрос:

  • 🔒 с ВЫБРАТЬ РАЗРЕШЕННЫЕ недоступные строки молча исключаются — выборка может оказаться пустой;
  • 🔒 без РАЗРЕШЕННЫЕ запрос, задевший запрещённые записи, падает с ошибкой о недостаточных правах.

Порядок проверки:

  1. Выполните тот же запрос под администратором с полными правами, например в консоли запросов.
  2. Если строки появились — дело в правах, а не в данных.
  3. Посмотрите, какие профили групп доступа и роли у пользователя, и какие ограничения на эти таблицы в них есть.
  4. Проверьте, к каким группам доступа относится сам объект: организация, склад, вид контрагента.

УстановитьПривилегированныйРежим(Истина) на сервере снимает ограничения для кода. Включайте его только там, где пользователь не должен видеть данные напрямую, — например, при служебной проверке существования. Привилегированный режим ради того, чтобы «запрос начал работать», открывает пользователю данные, которые ему закрыли намеренно.

Скорость поиска объектов: индексы и проверка существования

Проверка существования должна быть дешёвой. Если она тормозит, причина обычно в одном из трёх мест.

⚡ Что ускоряет поиск:

  • 🔑 поиск по Ссылка идёт по основному индексу таблицы и выполняется быстро всегда;
  • 🔑 у справочников код и наименование индексируются автоматически;
  • 🔑 для реквизита, по которому часто ищут (ИНН, артикул), в конфигураторе ставят свойство Индексировать в значение «Индексировать» или «Индексировать с доп. упорядочиванием»;
  • 🔑 ПЕРВЫЕ 1 останавливает чтение на первой найденной строке.

🐢 Что замедляет:

  • 🔁 запрос в цикле на каждую ссылку. Соберите ссылки в массив и проверьте одним запросом с условием Ссылка В (&МассивСсылок);
  • 🔁 обращение через точку к полю составного типа. Платформа соединяет все таблицы из состава типа. Помогает ВЫРАЗИТЬ(Поле КАК Справочник.Контрагенты);
  • 🔁 функции над индексированным полем в условии, например ПОДСТРОКА(ИНН, 1, 4). Индекс при этом не используется.

Проверка массива ссылок одним запросом:

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

Для Каждого Ссылка Из МассивСсылок Цикл
	Если Найденные.Найти(Ссылка) = Неопределено Тогда
		Сообщить("Объект не найден: " + Ссылка.УникальныйИдентификатор());
	КонецЕсли;
КонецЦикла;

Для тысяч ссылок вместо поиска по массиву удобнее передать их таблицей значений во временную таблицу и сделать соединение — так сравнение выполнит СУБД.

Частые ошибки при проверке

  • ❌ ЗначениеЗаполнено() как проверка существования. Битая ссылка не пустая, функция вернёт Истина.
  • ❌ Проверка Ссылка.Пустая(). То же самое: она отвечает на вопрос «пустая ли ссылка», а не «есть ли объект».
  • ❌ Удаление объектов методом Удалить() в обработках. Это и есть главный источник битых ссылок. Ставьте пометку удаления и удаляйте через «Удаление помеченных объектов» с контролем ссылок.
  • ❌ Очистка всех битых ссылок одним махом. Проведённые документы без контрагента или номенклатуры ломают взаиморасчёты и остатки. Сначала решите по каждому типу, что восстановить.
  • ❌ Поиск по строке из формы. Текст <Объект не найден> (45:…) — представление, а не идентификатор. Идентификатор берите из кода: Ссылка.УникальныйИдентификатор().

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

Почему битая ссылка появилась после обмена?
Документ загрузился раньше справочника, или справочник не попал в правила выгрузки. Повторная синхронизация часто догружает недостающие объекты, и ссылки «оживают» сами.
Можно ли восстановить удалённый объект с той же ссылкой?
Да. Создайте новый элемент, вызовите УстановитьСсылкуНового() с битой ссылкой и запишите. Документы снова увидят этот объект, но реквизиты придётся заполнить вручную или взять из резервной копии.
Поможет ли консоль запросов найти битые ссылки?
Да. Запрос с ЛЕВЫМ СОЕДИНЕНИЕМ и условием ЕСТЬ NULL выполняется в любой консоли запросов, отдельная обработка не нужна.
Что вернёт обращение к реквизиту битой ссылки внутри запроса?
NULL. Поле Документ.Контрагент.Наименование для несуществующего контрагента будет NULL, поэтому ЕСТЬ NULL по нему тоже находит битые ссылки.
Почему запрос в отладчике находит данные, а в отчёте нет?
Отчёт может запускаться под другим пользователем, с другими параметрами или с отбором из настроек. Сравните значения параметров и пользователя в обоих случаях.