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