Поиск битых ссылок запросом. Универсальный алгоритм и обработка-пример.

Поиск битых ссылок запросом. Универсальный алгоритм и обработка-пример.

Обычно, для поиска битых ссылок используется следующий метод. Записи таблицы выбираются программно через метод менеджера объекта или регистра Выбрать() и строковое представление ссылки сравнивается со строкой "Объект не найден " . Обход выборки представляет собой порциональное чтение записей из базы, то есть запрос в цикле. Затем еще один запрос на каждой итерации для получения представления. И в случае универсального алгоритма представление нужно получить для каждого ссылочного поля.

Мы рассмотрим другой, более быстрый и универсальный способ - поиск через запрос к БД.

Казалось бы, что может быть проще? Мы знаем, что представление битой ссылки содержит " Объект не найден ". Достаточно в запросе получать представление ссылки и сравнивать его с этой строкой. Но функция языка запросов ПРЕДСТАВЛЕНИЕ() возвращает поле неограниченной длины и запросе мы не можем сделать такое сравнение. Попытка написать выражение ПОДСТРОКА(ПРЕДСТАВЛЕНИЕ(Ссылка) , 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 )); КонецЕсли; КонецЦикла; КонецПроцедуры

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

Далее эти ссылки можно выгружать в файл для поиска объектов в архивной копии базы или создавать для них новые объекты. Но это уже совсем другая история :)

Если вам показалась интересной эта информация, то не пропустите также следующие публикации:

📎📎📎📎📎📎📎📎📎📎