Обычно, для поиска битых ссылок используется следующий метод. Записи таблицы выбираются программно через метод менеджера объекта или регистра Выбрать() и строковое представление ссылки сравнивается со строкой "Объект не найден". Обход выборки представляет собой порциональное чтение записей из базы, то есть запрос в цикле. Затем еще один запрос на каждой итерации для получения представления. И в случае универсального алгоритма представление нужно получить для каждого ссылочного поля.
Мы рассмотрим другой, более быстрый и универсальный способ - поиск через запрос к БД.
Казалось бы, что может быть проще? Мы знаем, что представление битой ссылки содержит "Объект не найден". Достаточно в запросе получать представление ссылки и сравнивать его с этой строкой. Но функция языка запросов ПРЕДСТАВЛЕНИЕ() возвращает поле неограниченной длины и запросе мы не можем сделать такое сравнение. Попытка написать выражение ПОДСТРОКА(ПРЕДСТАВЛЕНИЕ(Ссылка) , 1, 8) также не даст результата. Выполнение запроса прекратится с ошибкой:
Однако у битой ссылки в запросе есть и другое свойство. Ссылка, полученная в запросе от битой ссылки будет NULL. То есть если СсылочноеПоле указывает на несуществующий объект, то СсылочноеПоле.Ссылка - это NULL. Причем Ссылка - это единственное универсальное поле подходящее для такого сравнения. Код, Номер, Наименование и прочие поля могут отствовать у объектов.
Отлично, значит теперь мы можем выбрать битые ссылки? Не совсем. Ведь тем же свойством будут обладать пустые ссылки и более того, значение НЕОПРЕДЕЛЕНО, если поле в таблице базы данных имеет составной тип. То есть нужно проводить не только сравнение СсылочноеПоле.Ссылка есть NULL но и сравнивать СсылочноеПоле с пустыми ссылками допустимых типов и с НЕОПРЕДЕЛЕНО, исключая такие ссылки из результата запроса.
Звучит сложно? Действительно, если таблица содержит поля составного типа и мы будетм писать запрос вручную, то да. Возьмем например запрос для поиска битых ссылок в одном из регистров УПП 1.3. Он написан вручную и даже после выкидывания из него большей части полей выглядит громоздким. Добавление каждого нового поля приводит к появлению еще около десяти строк кода:
ВЫБРАТЬ
А.РазделУчета,
А.Организация,
А.Проект,
ВЫБОР
КОГДА А.РазделУчета.Ссылка ЕСТЬ NULL
И А.РазделУчета <> ЗНАЧЕНИЕ(Перечисление.РазделыУчета.ПустаяСсылка)
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК РазделУчетаЭтоБитаяСсылка,
ВЫБОР
КОГДА А.Организация.Ссылка ЕСТЬ NULL
И А.Организация <> ЗНАЧЕНИЕ(Справочник.Организации.ПустаяСсылка)
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК ОрганизацияЭтоБитаяСсылка,
ВЫБОР
КОГДА А.Проект.Ссылка ЕСТЬ NULL
И А.Проект <> ЗНАЧЕНИЕ(Справочник.Проекты.ПустаяСсылка)
И А.Проект <> ЗНАЧЕНИЕ(Справочник.ВидыРаспределенияПоПроектам.ПустаяСсылка)
И А.Проект <> НЕОПРЕДЕЛЕНО
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК ПроектЭтоБитаяСсылка
ИЗ
РегистрСведений.АналитикаВидаУчета КАК А
ГДЕ
(А.РазделУчета.Ссылка ЕСТЬ NULL
И А.РазделУчета <> ЗНАЧЕНИЕ(Перечисление.РазделыУчета.ПустаяСсылка)
ИЛИ А.Организация.Ссылка ЕСТЬ NULL
И А.Организация <> ЗНАЧЕНИЕ(Справочник.Организации.ПустаяСсылка)
ИЛИ А.Проект.Ссылка ЕСТЬ NULL
И А.Проект <> ЗНАЧЕНИЕ(Справочник.Проекты.ПустаяСсылка)
И А.Проект <> ЗНАЧЕНИЕ(Справочник.ВидыРаспределенияПоПроектам.ПустаяСсылка)
И А.Проект <> НЕОПРЕДЕЛЕНО)
Но если присмотреться, то мы увидим, что его формирование сводится к алгоритму всего из двух циклов. Внешнего - по полям таблицы и вложенного - по типам каждого поля. Причем этот алгоритм будет подходить для любой таблицы базы данных. После чего за одно обращение к БД мы получим все битые ссылки из таблицы, а дальше обойдем результат запроса, уже не обращаясь к базе.
Алгоритм на языке близком к человеческому звучит так:
1) Перебираем ссылочные поля объекта метаданных и добавляем их в поля запроса.
2) Для каждого добавленного в п.1 поля создаем в запросе еще одно выражение, которое будет принимать значение ИСТИНА, если поле содержит битую ссылку. Это выражение формируется путем перебора доступных для поля типов и сравнения поля с НЕОПРЕДЕЛНО и пустой ссылкой для каждого типа: ЗНАЧЕНИЕ(ТаблицаТипа.ПустаяСсылка).
3) Из базы данных нам нужно выбирать только те записи в которых хотя бы одно поле - это битая ссылка. Поэтому такие же выражения как в п.2 добавляем в блок запроса ГДЕ, объединяя эти выражения оператором ИЛИ.
4) Выполняем запрос и обходя его результат выбираем те значения полей, для которых выражение построенное в п.2 принимает значение ИСТИНА.
А вот и реализация алгоритма на языке 1С. Здесь ПолноеИмяТаблицы - это имя таблицы базы данных (например "Документ.АвансовыйОтчет.Товары"), а МД - это метаданные таблицы (объекта, табличной части или регистра):
Процедура НайтиСсылкиНаСервере(МД, ПолноеИмяТаблицы)
//массив будет содержать структуры с четырмя элементами:
//1) Поле - имя и псевдоним в запросе ссылочного поля таблицы
//2) ПолеЭтоБитаяСсылка - псевдоним в запросе поля булевого типа, которое
// в результате запроса будет Истина, если Поле содержит битую ссылку
//3) МассивИменТаблиц - массив, состоящий из полных имен метаданных,
// на которые возможны ссылки из поля
//4) МожетБытьНеопределено - может ли поле быть равно Неопределено
МассивОписанийПолей = Новый Массив;
ДобавитьОписаниеПолей(МассивОписанийПолей, "Измерения", МД);
ДобавитьОписаниеПолей(МассивОписанийПолей, "Ресурсы", МД);
ДобавитьОписаниеПолей(МассивОписанийПолей, "Реквизиты", МД);
ДобавитьОписаниеПолей(МассивОписанийПолей, "РеквизитыАдресации", МД);
Если МассивОписанийПолей.Количество() = 0 Тогда
Возврат; //ссылочных полей нет
КонецЕсли;
//Теперь у нас есть ссылочные поля таблицы и имена таблиц, ссылки на которые
//они могут содержать можно переходить к конструированию запроса
БлокСсылочныхПолей = "";
БлокБулевыхПолей = "";
БлокУсловия = "";
ПС = Символы.ПС;
ТАБ = Символы.Таб;
ТАБ3 = ТАБ+ТАБ+ТАБ;
МаксИндексМассиваОписаний = МассивОписанийПолей.Количество() - 1;
Для К = 0 По МаксИндексМассиваОписаний Цикл
ОписаниеПоля = МассивОписанийПолей[К];
БулевоВыражение = "ВЫБОР КОГДА " + ПС+ТАБ3+
?(ОписаниеПоля.МожетБытьНеопределено, ОписаниеПоля.Поле + " <> НЕОПРЕДЕЛЕНО И ", "");
Для Каждого ИмяТаблицы Из ОписаниеПоля.МассивИменТаблиц Цикл
БулевоВыражение = БулевоВыражение + ОписаниеПоля.Поле
+ " <> ЗНАЧЕНИЕ("+ИмяТаблицы+".ПустаяСсылка) И ";
КонецЦикла;
БулевоВыражение = БулевоВыражение+ОписаниеПоля.Поле+".Ссылка ЕСТЬ NULL"
+ПС+ТАБ3+"ТОГДА ИСТИНА ИНАЧЕ ЛОЖЬ КОНЕЦ";
БлокСсылочныхПолей = БлокСсылочныхПолей + ТАБ + ОписаниеПоля.Поле;
БлокБулевыхПолей = БлокБулевыхПолей + ТАБ + БулевоВыражение + " КАК "+ОписаниеПоля.ПолеЭтоБитаяСсылка;
БлокУсловия = БлокУсловия + ТАБ + БулевоВыражение;
Если К <> МаксИндексМассиваОписаний Тогда //дальше будут еще поля
БлокСсылочныхПолей = БлокСсылочныхПолей + ","+ПС;
БлокБулевыхПолей = БлокБулевыхПолей + "," + ПС;
БлокУсловия = БлокУсловия + " ИЛИ " + ПС;
КонецЕсли;
КонецЦикла;
//СОБИРАЕМ ТЕКСТ, ДОБАВЛЯЕМ ТАБЫ И ПЕРЕНОСЫ ЧТОБЫ БЫЛО КРАСИВО
ТекстЗапроса = "ВЫБРАТЬ" +ПС+ПС+ БлокСсылочныхПолей + ","+ПС+БлокБулевыхПолей+
ПС+ПС+"ИЗ " + ПолноеИмяТаблицы+
ПС+ПС+"ГДЕ"+ПС+ПС+ БлокУсловия;
Запрос = Новый Запрос(ТекстЗапроса);
Выборка = Запрос.Выполнить().Выбрать();
//Обходим записи с битыми ссылками и по булевым полям смотрим, какие именно поля содержат битые ссылки
Пока Выборка.Следующий() Цикл
Для Каждого ОписаниеПоля Из МассивОписанийПолей Цикл
Если Выборка[ОписаниеПоля.ПолеЭтоБитаяСсылка] Тогда //ИСТИНА, значит ссылка битая
БитаяСсылка = Выборка[ОписаниеПоля.Поле];
Сообщить(БитаяСсылка); //ГОТОВО!!! ЗДЕСЬ ЧТО-ТО ДЕЛАЕМ С БИТОЙ ССЫЛКОЙ
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецПроцедуры
//ВСПОМОГАТЕЛЬНЫЙ МЕТОД ДЛЯ ПОЛУЧЕНИЯ ОПИСАНИЯ ПОЛЕЙ ИЗ МЕТАДАННЫХ
Процедура ДобавитьОписаниеПолей(МассивОписанийПолей, ТипПолей, МД)
Попытка
Поля = МД[ТипПолей];
Исключение //если возникло исключение значит у этой таблицы
Возврат; //нет такого типа полей и нам не нужно их обходить
КонецПопытки;
Для Каждого Поле Из Поля Цикл
ТипыПоля = Поле.Тип.Типы();
МассивПолныхИменМетаданных = Новый Массив;
Для Каждого Тип Из ТипыПоля Цикл
МетаданныеТипа = Метаданные.НайтиПоТипу(Тип);
Если МетаданныеТипа <> Неопределено Тогда
МассивПолныхИменМетаданных.Добавить(МетаданныеТипа.ПолноеИмя());
КонецЕсли;
КонецЦикла;
Если МассивПолныхИменМетаданных.Количество() > 0 Тогда //Тип поля допускает хранение в нем ссылок
МассивОписанийПолей.Добавить(Новый Структура(
"Поле, ПолеЭтоБитаяСсылка, МассивИменТаблиц, МожетБытьНеопределено",
Поле.Имя, Поле.Имя + "ЭтоБитаяСсылка", МассивПолныхИменМетаданных, ТипыПоля.Количество()>1));
КонецЕсли;
КонецЦикла;
КонецПроцедуры
К публикации прилагается обработка, в которой реализован этот алгоритм. Выбирается класс метаданных, объект метаданных и его таблица. Для демонстрации результата найденные ссылки помещаются в дерево значений с группировкой по типам ссылок.
Далее эти ссылки можно выгружать в файл для поиска объектов в архивной копии базы или создавать для них новые объекты. Но это уже совсем другая история :)
Если вам показалась интересной эта информация, то не пропустите также следующие публикации:
Динамические подписки на события
Универсальный динамический список с возможностью отбора по реквизитам табличных частей