Обычно, для поиска битых ссылок используется следующий метод. Записи таблицы выбираются программно через метод менеджера объекта или регистра Выбрать() и строковое представление ссылки сравнивается со строкой «Объект не найден«. Обход выборки представляет собой порциональное чтение записей из базы, то есть запрос в цикле. Затем еще один запрос на каждой итерации для получения представления. И в случае универсального алгоритма представление нужно получить для каждого ссылочного поля.
Мы рассмотрим другой, более быстрый и универсальный способ — поиск через запрос к БД.
Казалось бы, что может быть проще? Мы знаем, что представление битой ссылки содержит «Объект не найден«. Достаточно в запросе получать представление ссылки и сравнивать его с этой строкой. Но функция языка запросов ПРЕДСТАВЛЕНИЕ() возвращает поле неограниченной длины и запросе мы не можем сделать такое сравнение. Попытка написать выражение ПОДСТРОКА(ПРЕДСТАВЛЕНИЕ(Ссылка) , 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));
КонецЕсли;
КонецЦикла;
КонецПроцедуры
К публикации прилагается обработка, в которой реализован этот алгоритм. Выбирается класс метаданных, объект метаданных и его таблица. Для демонстрации результата найденные ссылки помещаются в дерево значений с группировкой по типам ссылок.
Далее эти ссылки можно выгружать в файл для поиска объектов в архивной копии базы или создавать для них новые объекты. Но это уже совсем другая история 🙂
Если вам показалась интересной эта информация, то не пропустите также следующие публикации:
Динамические подписки на события
Универсальный динамический список с возможностью отбора по реквизитам табличных частей
(0) Как ваш запрос дружит с RLS? Какова будет реакция, если в запрос попадет объект не доступный из-за RLS?
(2) B2B,
Цель публикации — описать алгоритм и предоставить обработку-шаблон, которую можно менять под свои нужды.
Если так получилось, что поиском битых ссылок и восстановлением объектов для них у вас занимается пользователь с ограниченными правами, то нужно изменить код в зависимости от ситуации. Либо добавить в начало метода НайтиСсылкиНаСервере вызов УстановитьПривилегированныйРежим, либо добавить в запрос слово РАЗРЕШЕННЫЕ.
Также результат запроса будет содержать данные всех записей таблицы, в которых обнаружены битые ссылки. Если цель — именно восстановление объектов, то нужно обеспечить уникальность ссылок — выгружать битые ссылки в таблицу значений и сворачивать таблицу по ним, либо модифицировать запрос c этой целью. Это уже вам решать.
Ценю фундаментальность!
Как узнать, является значение ссылочного типа «битой ссылкой» или нет?
Особенно глобальную!!!
—
Вот еще один способ, не такой глобально-фундаментальный,
но тоже универсальный
Нужно ли добавлять номер строки, где есть битая ссылка, в случае перебора табличной части объекта?
(5) Makushimo,
Это имеет смысл, если вы хотите делать замену битой ссылки на другую или просто увидеть глазами, где именно она была использована.
В этом случае необходимо также выбирать все поля, которые составляют ключ таблицы БД. Для объекта — это Ссылка, для табличной части Ссылка+НомерСтроки, для независимых регистров — комбинация их измерений и т.д.
Хотя замена ссылки или поиск объектов, где она была использована, обычно производятся глобально и для этого используются другие методы.
Чтобы не делать трехэтажные выборки можно получить представление полей со ссылочным типом и выгрузить в ТЗ. Затем эту таблицу значений поместить во временную таблицу и вторым запросом выбрать с условием ПОДОБНО «%<Объект не найден>%».
Не хватает колонки «Ссылка», чтобы оперативно увидеть в каком документе/справочнике обнаружена битая ссылка. А так да, заслуженный плюс
(7) TheGrr,
Ого! Или я неправильно понял, или Вы предлагаете прочитать всю таблицу и не фильтруя записи передать ее из SQL в 1С. Затем передать обратно из 1С в SQL в качестве параметра. Создать из нее временную таблицу и поместить в нее все эти данные, раздувая tempdb. Затем еще раз пробежаться по ней оператором ПОДОБНО и передать результат из SQL в 1С. Это точно оптимальный алгоритм? 🙂
(9) а я не говорил, что алгоритм оптимальнее. Я говорил, что выборка будет читабельнее 😀
(2) B2B, Битые ссылки, обычно, интересуют тех, на кого RLS не распространяются 🙂
Хороший алгоритм. Плюс без разговоров.
(0) Даже глянув наискосок видно что написано супер, уважаю такой подход, да и тема актуальная
(0) есть несколько пожеланий: 1. в выборку попадают не только ссылочные объекты, но и простые(типа Число, Дата и т.д.)-это плохо 2. «Не царское это дело» выбирать 3х этажную конструкцию сначала Справочник.Класс, Справочник.Объект,Справочник.Таблица — проще выбрать класс и прогнать по всему классу, а потом уже копаться в отдельно взятом (в табличную часть обработки добавить ссылку на объект)
Претензия на универсальность была бы уместна, если бы был список метаданных (документы, справочники, регистры), в которых искать с галочками.
А искать по каждой таблице отдельно, тем более в УПП — шутка ли ?
(16) 7OH,
Да без проблем. Подкорректировал заголовок. Слово «универсальность» относилось к алгоритму, поскольку он применим для любой таблицы базы данных. Обработка — просто пример его реализации (см. комментарий №3)
выбрать *
из справочник.контрагенты
где ссылка не в (выбрать ссылка
из справочник.контрагенты)
Столько восхищений удивлений, аБитые ссылки. Поиск, удаление, восстановление. разве никто не видел?
а разве силами самого конфигуратора не можно тоже самое сделать? я с 1с-кой только знакомлюсь и как раз только вчера вылавливал битые ссылки, и с помощью конфигуратора — отлично все получается
(20)Вы имеете ввиду ТиИ? Это не тоже самое 🙂 У меня в базе очень часто бывает, что нет ссылок ибо РИБ и ходят не все объекты, ТиИ натворит такой бардак, что страшно подумать. Поэтому иногда удобнее бить прицельно. То есть я знаю, где ссылка реально битая, а где она заменится объектом после обмена. Еще это имеет смысл, для перерегистрации объектов. То есть в этой базе они битые, зато в другой базе живые, если это РИБ просто перезаписал найденные ссылки в другой базе, если бэкап, выгрузил и загрузил в боевую. И как бы не всегда надо их исправлять, иногда к примеру их надо просто исключить из обработки.
(19)Потому что здесь запрос?
(22) webester,
Желтокнижник привел ссылку на очень хорошую универсальную обработку и в ней битые ссылки ищутся похожим методом. Но там не описан сам алгоритм и если спросить Яндекс или Гугл про поиск битых ссылок запросом, то на эту обработку вы не выйдите. Не вышел и я, поэтому и появилась эта публикация.
(22) webester, он и там(запрос), причем как мне помниться алгоритм точно такой же.
(24)ок, замечательная действительно обработка, зато здесь код сразу в статье, можно не тратить $m (если это для кого то критично), а самому собрать велосипед. По моему тоже имеет право на жизнь.
(23)либо у нас с вами гугл разный, либо вы им пользоваться не умеете, третья сверху ссылка.
(25) webester, у кого нет $m таких велосипедов насобирают.
(26)В целом согласен, но у человека может просто не быть здесь аккаунта. Как например у моих коллег, почему то не считают нужным, почему не знаю.
(25) webester, Вы прям напугали картинкой. Думал инфостарт глючит 🙂 По такому запросу будут десятки результатов. Вы забылидобавить одно ключевое слово к запросу 🙂
(28) я сначала посмотрел бы все варианты, а потом начал сужать круг. Но это же неважно по вашему запросу, все тоже отлично находится
(10) TheGrr, насколько она будет читабельнее в базе 150 ГБ и более ещё неизвестно, особо когда обработка будет выполняться неопределённо долгое время и потребует неопределенное количество ресурсов для выполнения алгоритма (очень похожего на китайский)
От себя добавлю, Афтор исправь алгоритм в обработке, будет работать неправильно при следующих условиях:
Проверяемое поле Имеет составной тип: несколько ссылочных, и один или более простых. По текущему алгоритму проверяется значение ссылки на null (наличие самой ссылки) но если там указан простой тип то её и не будет и не должно быть — однако такое значение успешно пройдёт проверку и вывалится в итоге как битая ссылка чем она в принципе не является.
Хотелось бы вариант обработки для обычного приложения а не только для управляемых форм.
Вопрос по управляемой форме, может ли эта обработка найти битые ссылка в отчетах? Например, в отборе характеристики в отчете аптеки стоит «<Объект не найден> (480:aee70015e9b8c48d11e01fecb9c33de3)».
А где-же в результате сами объекты в которых есть битые ссылки?
Такой алгоритм не будет работать на большой базе, где есть реквизит типа «Любая ссылка» и много-много разных ссылочных таблиц. Придется:
1. разбивать на подзапросы
2. делать явное левое соединение либо ВЫРАЗИТЬ
(34)Сами объекты можно найти например обработкой Универсальный подбор и обработка объектов (через поиск ссылок на объект). Знаю, что битую ссылку не добавить для поиска, но немного доработав обработку, это можно сделать.