Битая ссылка или пустая выборка: диагностика запросов в 1С 8.3
Содержание
Чтобы проверить, что объект не найден, в запросе 1С соединяют поле-ссылку с таблицей объекта и отбирают строки, где Таблица.Ссылка ЕСТЬ NULL. Пустой результат запроса проверяют методом РезультатЗапроса.Пустой(), а существование одной ссылки — запросом ВЫБРАТЬ ПЕРВЫЕ 1 по полю Ссылка. Функция ЗначениеЗаполнено() для такой проверки не годится: битая ссылка для неё «заполнена».
Это две разные ситуации, и их часто путают. Пустая выборка — запрос отработал, но условия не нашли строк. «Объект не найден» — в поле лежит ссылка на запись, которой в базе нет. Лечатся они по-разному, поэтому сначала разберитесь, какая из них у вас.
Откуда берутся пустая выборка и «Объект не найден»
Ссылка в 1С хранит тип и уникальный идентификатор объекта. Если запись с таким идентификатором удалили или не загрузили, в форме вместо значения появляется строка вида <Объект не найден> (45:b2c4...). Число до двоеточия — внутренний номер таблицы, дальше — идентификатор во внутреннем представлении.
🔍 Типичные причины битых ссылок:
- 🗑️ объект удалили напрямую методом
Удалить()— без контроля ссылок, в обход «Удаления помеченных объектов»; - 🔄 при обмене данными пришёл документ, а справочник, на который он ссылается, не выгружен или ещё не загружен;
- 📦 загрузили часть данных через XML или внешнюю обработку
, где записи шли с режимом
ОбменДанными.Загрузка = Истина; - 💾 восстановили базу или отдельные таблицы из разных копий;
- 🧩 удалили объект метаданных или изменили тип реквизита, а старые данные остались.
🔍 Причины пустой выборки при живых данных:
- ❌ параметр передан не того типа: строка вместо ссылки, дата без времени;
- ❌ условие на поле присоединённой таблицы стоит в
ГДЕ, а не вПО; - ❌ сравнение с
NULLчерез=вместоЕСТЬ NULL; - ❌ у пользователя ограничен доступ к записям, а запрос написан с
РАЗРЕШЕННЫЕ.
Как проверить в коде, что запрос ничего не вернул
Самый быстрый способ — спросить у результата, есть ли в нём строки. Выгружать его в таблицу ради этого не нужно.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.ИНН = &ИНН";
Запрос.УстановитьПараметр("ИНН", ИНН);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Сообщить("Контрагент с ИНН " + ИНН + " не найден");
Возврат;
КонецЕсли;
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
// обработка найденных строк
КонецЦикла;
Если строк может быть несколько, а нужна одна, используйте Если Выборка.Следующий() Тогда … Иначе … КонецЕсли. Метод Следующий() вернёт Ложь на пустой выборке, и ветка Иначе обработает случай «не найдено».
Сравнение способов:
| Способ | Когда применять | Подвох |
|---|---|---|
РезультатЗапроса.Пустой() | Нужно только знать, есть ли строки | Не говорит, сколько строк |
Выборка.Следующий() в Если | Нужна первая строка или «не найдено» | Остальные строки не читаются |
Выгрузить().Количество() = 0 | Таблица всё равно нужна дальше | Лишняя память, если таблица не нужна |
НайтиПоКоду(), НайтиПоНаименованию() | Поиск одного элемента справочника | Возвращают пустую ссылку, проверяйте .Пустая() |
У НайтиПоНаименованию() второй параметр — точное соответствие. Без него поиск идёт по началу строки, и на «ООО Ромашка» найдётся «ООО Ромашка-Плюс».
Параметры и условия отбора: где теряются строки
Больше всего пустых выборок дают параметры. Запрос не ругается, если в параметр пришло значение не того типа, — он просто ничего не находит.
Тип и значение параметра
- Поставьте точку останова на строке
Запрос.Выполнить(). - В отладчике раскройте
Запрос.Параметрыи проверьте тип каждого значения. - Частая ошибка — в параметр ушло представление ссылки (строка), а сравнивается поле ссылочного типа.
- Если параметр не установлен вовсе, запрос завершится ошибкой, а не пустым результатом. Это хотя бы видно сразу.
Границы периода
Дата 31.12 без времени — это 31.12 0:00:00. Документы, проведённые днём 31 декабря, в выборку <= &КонецПериода не попадут. Передавайте КонецДня(Дата) или пишите в тексте запроса КОНЕЦПЕРИОДА(&КонецПериода, ДЕНЬ).
Условия на присоединённые таблицы
Условие в ГДЕ на поле таблицы, присоединённой через ЛЕВОЕ СОЕДИНЕНИЕ, превращает соединение во внутреннее. Строки, для которых справа ничего не нашлось, отсекаются.
// Плохо: потеряются контрагенты без договоров
ГДЕ Договоры.ВидДоговора = &Вид
// Хорошо: условие в соединении
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.ДоговорыКонтрагентов КАК Договоры
ПО Договоры.Владелец = Контрагенты.Ссылка
И Договоры.ВидДоговора = &Вид
Виртуальные таблицы регистров
Отбор для Остатки и ОстаткиИОбороты пишите в параметрах виртуальной таблицы, а не в ГДЕ. Результат может совпасть, но в параметрах платформа фильтрует данные до расчёта, и запрос работает быстрее.
Сравнение с NULL
Поле = NULL никогда не даёт истину. Для проверки отсутствия значения нужно Поле ЕСТЬ NULL, а чтобы подставить значение по умолчанию — ЕСТЬNULL(Поле, 0).
Как найти и исправить битые ссылки запросом
Проверка «объект не найден» в запросе строится на соединении. Для битой ссылки справа ничего не находится, и поле Ссылка правой таблицы получает NULL.
ВЫБРАТЬ
Реализация.Ссылка КАК Документ,
Реализация.Контрагент КАК БитаяСсылка
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
ПО Реализация.Контрагент = Контрагенты.Ссылка
ГДЕ
Контрагенты.Ссылка ЕСТЬ NULL
И Реализация.Контрагент <> ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
Второе условие отсекает пустые поля: пустая ссылка тоже не найдётся в справочнике, но битой она не считается. Если поле составного типа, добавьте ВЫРАЗИТЬ или проверку ТИПЗНАЧЕНИЯ(), чтобы отбирать ссылки только нужного типа.
Проверка одной ссылки
Для одной ссылки достаточно короткого запроса по индексу:
Функция СсылкаСуществует(Ссылка)
Если Не ЗначениеЗаполнено(Ссылка) Тогда
Возврат Ложь;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст = СтрШаблон(
"ВЫБРАТЬ ПЕРВЫЕ 1 ИСТИНА ИЗ %1 ГДЕ Ссылка = &Ссылка",
Ссылка.Метаданные().ПолноеИмя());
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Возврат Не Запрос.Выполнить().Пустой();
КонецФункции
В типовых конфигурациях на БСП похожая функция уже есть — ОбщегоНазначения.СсылкаСуществует(). Своя нужна, если БСП в конфигурации нет.
Ссылка.ПолучитьОбъект() на битой ссылке возвращает Неопределено. Это тоже проверка, но она читает объект целиком вместе с табличными частями. Для массовой проверки берите запрос.
Что делать с найденными ссылками
| Вариант | Когда подходит | Риск |
|---|---|---|
| Восстановить объект с той же ссылкой | Объект нужен, например контрагент в проведённых документах | Нужно заполнить реквизиты вручную |
| Заменить на правильный объект | Есть дубль или верный элемент | Перепроведение документов меняет движения |
| Очистить поле | Значение не нужно | Документ может перестать проводиться |
| Догрузить данные обменом | Ссылки пришли из другой базы | Нужно наладить сам обмен |
Восстановить объект с той же ссылкой можно кодом. Тогда все документы снова увидят своего контрагента:
Объект = Справочники.Контрагенты.СоздатьЭлемент();
Объект.УстановитьСсылкуНового(БитаяСсылка);
Объект.Наименование = "Восстановлен: уточнить";
Объект.Записать();
Проверка через конфигуратор
Для всей базы сразу есть Администрирование → Тестирование и исправление в конфигураторе. Включите проверку ссылочной целостности и выберите, что делать при ссылках на несуществующие объекты: создавать объекты, очищать ссылки или не изменять.
Перед тестированием и исправлением сделайте резервную копию базы. Режим требует монопольного доступа: всем пользователям нужно выйти, их сеансы будут недоступны до конца проверки. Запускайте сначала с вариантом «не изменять», чтобы увидеть масштаб, и только потом исправляйте.
Вариант «создавать объекты» создаёт пустые элементы-заглушки. После него пройдитесь по ним и заполните реквизиты, иначе в отчётах появятся безымянные строки.
Права доступа: почему у пользователя пусто, а у администратора нет
Запрос выполняется с правами текущего пользователя. Если в роли настроено ограничение доступа на уровне записей (RLS), результат зависит от того, как написан запрос:
- 🔒 с
ВЫБРАТЬ РАЗРЕШЕННЫЕнедоступные строки молча исключаются — выборка может оказаться пустой; - 🔒 без
РАЗРЕШЕННЫЕзапрос, задевший запрещённые записи, падает с ошибкой о недостаточных правах.
Порядок проверки:
- Выполните тот же запрос под администратором с полными правами, например в консоли запросов.
- Если строки появились — дело в правах, а не в данных.
- Посмотрите, какие профили групп доступа и роли у пользователя, и какие ограничения на эти таблицы в них есть.
- Проверьте, к каким группам доступа относится сам объект: организация, склад, вид контрагента.
УстановитьПривилегированныйРежим(Истина) на сервере снимает ограничения для кода. Включайте его только там, где пользователь не должен видеть данные напрямую, — например, при служебной проверке существования. Привилегированный режим ради того, чтобы «запрос начал работать», открывает пользователю данные, которые ему закрыли намеренно.
Скорость поиска объектов: индексы и проверка существования
Проверка существования должна быть дешёвой. Если она тормозит, причина обычно в одном из трёх мест.
⚡ Что ускоряет поиск:
- 🔑 поиск по
Ссылкаидёт по основному индексу таблицы и выполняется быстро всегда; - 🔑 у справочников код и наименование индексируются автоматически;
- 🔑 для реквизита, по которому часто ищут (ИНН, артикул), в конфигураторе ставят свойство
Индексироватьв значение «Индексировать» или «Индексировать с доп. упорядочиванием»; - 🔑
ПЕРВЫЕ 1останавливает чтение на первой найденной строке.
🐢 Что замедляет:
- 🔁 запрос в цикле на каждую ссылку. Соберите ссылки в массив и проверьте одним запросом с условием
Ссылка В (&МассивСсылок); - 🔁 обращение через точку к полю составного типа. Платформа соединяет все таблицы из состава типа. Помогает
ВЫРАЗИТЬ(Поле КАК Справочник.Контрагенты); - 🔁 функции над индексированным полем в условии, например
ПОДСТРОКА(ИНН, 1, 4). Индекс при этом не используется.
Проверка массива ссылок одним запросом:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Контрагенты.Ссылка КАК Ссылка
|ИЗ
| Справочник.Контрагенты КАК Контрагенты
|ГДЕ
| Контрагенты.Ссылка В (&Ссылки)";
Запрос.УстановитьПараметр("Ссылки", МассивСсылок);
Найденные = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
Для Каждого Ссылка Из МассивСсылок Цикл
Если Найденные.Найти(Ссылка) = Неопределено Тогда
Сообщить("Объект не найден: " + Ссылка.УникальныйИдентификатор());
КонецЕсли;
КонецЦикла;
Для тысяч ссылок вместо поиска по массиву удобнее передать их таблицей значений во временную таблицу и сделать соединение — так сравнение выполнит СУБД.
Частые ошибки при проверке
- ❌
ЗначениеЗаполнено()как проверка существования. Битая ссылка не пустая, функция вернётИстина. - ❌ Проверка
Ссылка.Пустая(). То же самое: она отвечает на вопрос «пустая ли ссылка», а не «есть ли объект». - ❌ Удаление объектов методом
Удалить()в обработках. Это и есть главный источник битых ссылок. Ставьте пометку удаления и удаляйте через «Удаление помеченных объектов» с контролем ссылок. - ❌ Очистка всех битых ссылок одним махом. Проведённые документы без контрагента или номенклатуры ломают взаиморасчёты и остатки. Сначала решите по каждому типу, что восстановить.
- ❌ Поиск по строке из формы. Текст
<Объект не найден> (45:…)— представление, а не идентификатор. Идентификатор берите из кода:Ссылка.УникальныйИдентификатор().
Частые вопросы
Почему битая ссылка появилась после обмена?
Можно ли восстановить удалённый объект с той же ссылкой?
УстановитьСсылкуНового() с битой ссылкой и запишите. Документы снова увидят этот объект, но реквизиты придётся заполнить вручную или взять из резервной копии.Поможет ли консоль запросов найти битые ссылки?
ЕСТЬ NULL выполняется в любой консоли запросов, отдельная обработка не нужна.Что вернёт обращение к реквизиту битой ссылки внутри запроса?
NULL. Поле Документ.Контрагент.Наименование для несуществующего контрагента будет NULL, поэтому ЕСТЬ NULL по нему тоже находит битые ссылки.