Каталог решений - Поиск битых ссылок

Поиск битых ссылок

Поиск битых ссылок

В наличии

Обработка для поиска всех битых ссылок (Объект не найден).

Категория:

Описание

Зачастую возникает необходимость поиска битых ссылок. Например, при обмене данными, или при неудачном удалении объектов.

Вводные данные такие: база около 1 Тб, битых ссылок по оценке десятки тысяч.

Поискав по просторам интернета, ничего идеально подходящего не нашлось, чтоб не уходило в бесконечность.

Однако очень близка к идеалу была статья, она и была взята за основу, только чуть подпилена.

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

Список доработок:

  1. Вместо выбора конкретных таблиц, сделана возможность выбора на форме классов таблиц (Документы, Справочники и т.д.):

 

 

  1. Добавлена возможность добавления таблиц исключений (например, на некоторые таблицы нет доступа даже у полных прав)

 

 

  1. Добавлено логирование, с заполнением текущей даты и последнего завершенного объекта (например, справочника). Возникла необходимость, так как порой уходит очень надолго, и выбран быстрый вариант доработки.
  2. Основной алгоритм в целом похож на исходную статью, из значимого:
  • добавил сразу проверку на то, что поле имеет один тип и это число.
  • вместо добавлением всего и вся «.Ссылка» сделал левое соединение только к тем таблицам, типы которых действительно встречаются в базе данных. Способа использовал два. Первый – если в поле может быть больше 5 типов, то использовал запрос:
Запрос.Текст = "Выбрать Различные ТипЗначения(" + Поле.Имя + ") Как Тип Из " + ПолноеИмяТаблицы;

Если в поле меньше 5 типов, то делал запросы перебором для каждого типа:

Запрос.Текст = "Выбрать Первые 1 Истина Из " + ПолноеИмяТаблицы + " Где ТипЗначения(" + Поле.Имя + ") = &Тип";

И если запрос не пустой, то делал соединение к этой таблице.

Несмотря на то, что здесь запрос в цикле, это в значительной степени окупается при большом объеме данных. Добавление «.Ссылка» в запрос уводило его в бесконечность. Явное соединение только с используемыми таблицами сократило до минимума.

  1. И еще некоторые дополнения.

Программный код основного алгоритма следующий.

Процедура НайтиСсылкиНаСервере собирает текст запроса и заполняет таблицу на форме с битыми ссылками.

&НаСервере
Процедура НайтиСсылкиНаСервере(МД, ПолноеИмяТаблицы, КэшИсключений)
	
	Если КэшИсключений[ПолноеИмяТаблицы] <> Неопределено Тогда
		Возврат;
	КонецЕсли;
		
	Запрос = Новый Запрос("Выбрать Первые 1 Истина Из " + ПолноеИмяТаблицы);
	Рез = Запрос.Выполнить();
	Если Рез.Пустой() Тогда
		Возврат;
	КонецЕсли;
	
    //массив будет содержать структуры с четырмя элементами:
    //1) Поле - имя и псевдоним в запросе ссылочного поля таблицы
    //2) ПолеЭтоБитаяСсылка - псевдоним в запросе поля булевого типа, которое
    //   в результате запроса будет Истина, если Поле содержит битую ссылку
    //3) МассивИменТаблиц - массив, состоящий из полных имен метаданных,
    //   на которые возможны ссылки из поля
    //4) МожетБытьНеопределено - может ли поле быть равно Неопределено
    МассивОписанийПолей = Новый Массив;
    ДобавитьОписаниеПолей(МассивОписанийПолей, "Измерения", МД, ПолноеИмяТаблицы);
    ДобавитьОписаниеПолей(МассивОписанийПолей, "Ресурсы", МД, ПолноеИмяТаблицы);
    ДобавитьОписаниеПолей(МассивОписанийПолей, "Реквизиты", МД, ПолноеИмяТаблицы);
    ДобавитьОписаниеПолей(МассивОписанийПолей, "РеквизитыАдресации", МД, ПолноеИмяТаблицы);

    Если МассивОписанийПолей.Количество() = 0 Тогда
        Возврат; //ссылочных полей нет
    КонецЕсли;

    //Теперь у нас есть ссылочные поля таблицы и имена таблиц, ссылки на которые
    //они могут содержать можно переходить к конструированию запроса
    ПС = Символы.ПС;
    ТАБ = Символы.Таб;
    ТАБ3 = ТАБ+ТАБ+ТАБ;

    МаксИндексМассиваОписаний = МассивОписанийПолей.Количество() - 1;
	ТекстЗапросаИтоговый = "";
    Для Г = 0 По МаксИндексМассиваОписаний Цикл
	    БлокСсылочныхПолей = "";
	    БлокБулевыхПолей = "";
	    БлокУсловия = "";
		ТекстСоединения = "";
		НаимТаб = "т_" + СтрЗаменить(Строка(Новый УникальныйИдентификатор), "-", "");
	    Для К = 0 По МаксИндексМассиваОписаний Цикл

	        ОписаниеПоля = МассивОписанийПолей[К];
			ТекстУсловияНул = "";
			Если Г <> К Тогда
				БулевоВыражение = "ЛОЖЬ";
			Иначе
		        БулевоВыражение = "ВЫБОР КОГДА " + ПС+ТАБ3+
		                ?(ОписаниеПоля.МожетБытьНеопределено, НаимТаб + "." + ОписаниеПоля.Поле + " <> НЕОПРЕДЕЛЕНО И ", "");

		        Для Каждого ИмяТаблицы Из ОписаниеПоля.МассивИменТаблиц Цикл
		            БулевоВыражение = БулевоВыражение + НаимТаб + "." + ОписаниеПоля.Поле
		                                + " <> ЗНАЧЕНИЕ("+ИмяТаблицы+".ПустаяСсылка) И ";
					ИмяСоедТаб = СтрЗаменить(ИмяТаблицы, ".", "_");
					ТекстСоединения = ТекстСоединения + ПС + "ЛЕВОЕ СОЕДИНЕНИЕ " + ИмяТаблицы + " КАК " + ИмяСоедТаб + ПС + "ПО " 
						+ НаимТаб + "." + ОписаниеПоля.Поле + " = " + ИмяСоедТаб + ".Ссылка";
					ТекстУсловияНул = ТекстУсловияНул + " И " + ИмяСоедТаб + ".Ссылка ЕСТЬ NULL"; 

				КонецЦикла;
				ТекстУсловияНул = Сред(ТекстУсловияНул, 4);
				
		        БулевоВыражение = БулевоВыражение+ТекстУсловияНул
		                            +ПС+ТАБ3+"ТОГДА ИСТИНА ИНАЧЕ ЛОЖЬ КОНЕЦ";
			КонецЕсли;							
								
	        БлокСсылочныхПолей = БлокСсылочныхПолей + ТАБ + НаимТаб + "." + ОписаниеПоля.Поле;
	        БлокБулевыхПолей = БлокБулевыхПолей + ТАБ + БулевоВыражение + " КАК "+ОписаниеПоля.ПолеЭтоБитаяСсылка;
	        БлокУсловия = БлокУсловия + ТАБ + БулевоВыражение;

	        Если К <> МаксИндексМассиваОписаний Тогда //дальше будут еще поля
	            БлокСсылочныхПолей = БлокСсылочныхПолей + ","+ПС;
	            БлокБулевыхПолей = БлокБулевыхПолей + "," + ПС;
	            БлокУсловия = БлокУсловия + " ИЛИ " + ПС;
			КонецЕсли;
			

		КонецЦикла;
	    //СОБИРАЕМ ТЕКСТ, ДОБАВЛЯЕМ ТАБЫ И ПЕРЕНОСЫ ЧТОБЫ БЫЛО КРАСИВО
	    ТекстЗапроса =  "ВЫБРАТЬ" +ПС+ПС+ БлокСсылочныхПолей + ","+ПС+БлокБулевыхПолей+
						//ПС+ПС+"ИЗ " + ПолноеИмяТаблицы+
	                    ПС+ПС+"ИЗ " + ПолноеИмяТаблицы+ " КАК "+НаимТаб+ТекстСоединения+
	                    ПС+ПС+"ГДЕ"+ПС+ПС+ БлокУсловия;
		ТекстЗапросаИтоговый = ТекстЗапросаИтоговый + ТекстЗапроса;
		Если Г <> МаксИндексМассиваОписаний Тогда //дальше будут еще поля
			ТекстЗапросаИтоговый = ТекстЗапросаИтоговый + ПС + ПС + "ОБЪЕДИНИТЬ ВСЕ" + ПС + ПС;
	    КонецЕсли;
			
	КонецЦикла;

   Запрос = Новый Запрос(ТекстЗапросаИтоговый);

	Попытка
    	Выборка = Запрос.Выполнить().Выбрать();
	Исключение
		Сообщить("Ошибка при обработке таблицы: " + ПолноеИмяТаблицы);
		ОписаниеОшибки = ОписаниеОшибки();
		Сообщить(ОписаниеОшибки);
	
	КонецПопытки;

    //Обходим записи с битыми ссылками и по булевым полям смотрим, какие именно поля содержат битые ссылки
	КэшСсылок = Новый Соответствие;
    Пока Выборка.Следующий() Цикл
        Для Каждого ОписаниеПоля Из МассивОписанийПолей Цикл
            Если Выборка[ОписаниеПоля.ПолеЭтоБитаяСсылка] Тогда //ИСТИНА, значит ссылка битая

                БитаяСсылка = Выборка[ОписаниеПоля.Поле];
				БитаяСсылкаСтрока = КэшСсылок[БитаяСсылка];
				Если БитаяСсылкаСтрока = Неопределено Тогда
					БитаяСсылкаСтрока = Строка(БитаяСсылка); 
					КэшСсылок.Вставить(БитаяСсылка, БитаяСсылкаСтрока);				
					Если СтрНачинаетсяС(БитаяСсылкаСтрока, "<Объект не найден>") Тогда	//Есть ошибка алгоритма, что если в составном поле есть строка и ссылка, то строка тоже идентифицируется как битая ссылка
						НовСтр = Табл.Добавить();
						НовСтр.БитаяСсылка = БитаяСсылкаСтрока;
					КонецЕсли;
				КонецЕсли;

			КонецЕсли;
        КонецЦикла;
    КонецЦикла;

КонецПроцедуры

Вспомогательная процедура ДобавитьОписаниеПолей определяет поля каких типов присутствуют в таблице и соответственно к каким таблицам требуется делать соединение.

&НаСервере
Процедура ДобавитьОписаниеПолей(МассивОписанийПолей, ТипПолей, МД, ПолноеИмяТаблицы)
	
    Попытка      
        Поля = МД[ТипПолей];
	Исключение  
		Возврат;
	КонецПопытки;

    Для Каждого Поле Из Поля Цикл
        ТипыПоля = Поле.Тип.Типы();
        МассивПолныхИменМетаданных = Новый Массив;
		Если ТипыПоля.Количество() = 1 И ТипыПоля[0] = Тип("Число") Тогда
			Продолжить;
		ИначеЕсли ТипыПоля.Количество() > 5 Тогда
	 		Запрос = Новый Запрос;
			Запрос.Текст = "Выбрать Различные ТипЗначения(" + Поле.Имя + ") Как Тип Из " + ПолноеИмяТаблицы;
			Рез = Запрос.Выполнить();
			ТипыПоля = Рез.Выгрузить().ВыгрузитьКолонку("Тип");
		Иначе
			ТипыПоляНовые = Новый Массив;
			Для Каждого Тип Из ТипыПоля Цикл
				Запрос = Новый Запрос;
				Запрос.Текст = "Выбрать Первые 1 Истина Из " + ПолноеИмяТаблицы + " Где ТипЗначения(" + Поле.Имя + ") = &Тип";
				Запрос.УстановитьПараметр("Тип", Тип);
				Рез = Запрос.Выполнить();
				Если Рез.Пустой() Тогда
					Продолжить;
				КонецЕсли;
				ТипыПоляНовые.Добавить(Тип);
			КонецЦикла;
			ТипыПоля = ТипыПоляНовые;
		КонецЕсли;

	    Для Каждого Тип Из ТипыПоля Цикл
			
	    	МетаданныеТипа = Метаданные.НайтиПоТипу(Тип);
	    	Если МетаданныеТипа <> Неопределено Тогда
	    	    МассивПолныхИменМетаданных.Добавить(МетаданныеТипа.ПолноеИмя());
	    	КонецЕсли;
	   КонецЦикла;

        Если МассивПолныхИменМетаданных.Количество() > 0 Тогда
            МассивОписанийПолей.Добавить(Новый Структура(
                "Поле, ПолеЭтоБитаяСсылка, МассивИменТаблиц, МожетБытьНеопределено",
                Поле.Имя, Поле.Имя + "ЭтоБитаяСсылка", МассивПолныхИменМетаданных, ТипыПоля.Количество()>1));
        КонецЕсли;
	КонецЦикла; 
	
КонецПроцедуры

Результат действия программы:

 

has been added to your cart:
Оформление заказа