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

Опубликовал Владимир Литвиненко (VladimirL) в раздел Программирование - Практика программирования

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

 

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

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

 

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

   

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

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

 

  

 

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

Динамические подписки на события

Универсальный динамический список с возможностью отбора по реквизитам табличных частей

 

 

Скачать файлы

Наименование Файл Версия Размер
ПоискБитыхСсылокВЗаданнойТаблице.epf
.epf 11,04Kb
17.11.13
198
.epf 11,04Kb 198 Скачать

См. также

Комментарии
2. B2B (B2B) 247 17.11.13 21:36 Сейчас в теме
(0) Как ваш запрос дружит с RLS? Какова будет реакция, если в запрос попадет объект не доступный из-за RLS?
3. Владимир Литвиненко (VladimirL) 557 18.11.13 02:20 Сейчас в теме
(2) B2B,
Цель публикации - описать алгоритм и предоставить обработку-шаблон, которую можно менять под свои нужды.

Если так получилось, что поиском битых ссылок и восстановлением объектов для них у вас занимается пользователь с ограниченными правами, то нужно изменить код в зависимости от ситуации. Либо добавить в начало метода НайтиСсылкиНаСервере вызов УстановитьПривилегированныйРежим, либо добавить в запрос слово РАЗРЕШЕННЫЕ.

Также результат запроса будет содержать данные всех записей таблицы, в которых обнаружены битые ссылки. Если цель - именно восстановление объектов, то нужно обеспечить уникальность ссылок - выгружать битые ссылки в таблицу значений и сворачивать таблицу по ним, либо модифицировать запрос c этой целью. Это уже вам решать.
4. Юрий Осипов (yuraos) 882 18.11.13 06:08 Сейчас в теме
Ценю фундаментальность!
Особенно глобальную!!!
---
Вот еще один способ, не такой глобально-фундаментальный,
но тоже универсальный
Как узнать, является значение ссылочного типа «битой ссылкой» или нет?
5. Максим Кузнецов (Makushimo) 149 18.11.13 06:45 Сейчас в теме
Нужно ли добавлять номер строки, где есть битая ссылка, в случае перебора табличной части объекта?
6. Владимир Литвиненко (VladimirL) 557 18.11.13 09:04 Сейчас в теме
(5) Makushimo,
Нужно ли добавлять номер строки, где есть битая ссылка, в случае перебора табличной части объекта

Это имеет смысл, если вы хотите делать замену битой ссылки на другую или просто увидеть глазами, где именно она была использована.

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

Хотя замена ссылки или поиск объектов, где она была использована, обычно производятся глобально и для этого используются другие методы.
7. Дмитрий Луканов (TheGrr) 110 18.11.13 12:07 Сейчас в теме
Чтобы не делать трехэтажные выборки можно получить представление полей со ссылочным типом и выгрузить в ТЗ. Затем эту таблицу значений поместить во временную таблицу и вторым запросом выбрать с условием ПОДОБНО "%<Объект не найден>%".
8. Maxim Kolkin (the1) 279 18.11.13 16:25 Сейчас в теме
Не хватает колонки "Ссылка", чтобы оперативно увидеть в каком документе/справочнике обнаружена битая ссылка. А так да, заслуженный плюс
9. Владимир Литвиненко (VladimirL) 557 18.11.13 17:26 Сейчас в теме
(7) TheGrr,
Чтобы не делать трехэтажные выборки можно получить представление полей со ссылочным типом и выгрузить в ТЗ. Затем эту таблицу значений поместить во временную таблицу и вторым запросом выбрать с условием ПОДОБНО "%<Объект не найден>%".

Ого! Или я неправильно понял, или Вы предлагаете прочитать всю таблицу и не фильтруя записи передать ее из SQL в 1С. Затем передать обратно из 1С в SQL в качестве параметра. Создать из нее временную таблицу и поместить в нее все эти данные, раздувая tempdb. Затем еще раз пробежаться по ней оператором ПОДОБНО и передать результат из SQL в 1С. Это точно оптимальный алгоритм? :)
10. Дмитрий Луканов (TheGrr) 110 18.11.13 17:33 Сейчас в теме
(9) VladimirL, а я не говорил, что алгоритм оптимальнее. Я говорил, что выборка будет читабельнее :D
11. Ловыгин Антон (wunderland) 196 18.11.13 19:27 Сейчас в теме
(2) B2B, Битые ссылки, обычно, интересуют тех, на кого RLS не распространяются :)
12. Петр (peterxx) 15 19.11.13 10:24 Сейчас в теме
Хороший алгоритм. Плюс без разговоров.
13. Олег Шалимов (CaSH_2004) 342 19.11.13 17:45 Сейчас в теме
(0) Даже глянув наискосок видно что написано супер, уважаю такой подход, да и тема актуальная
14. Сергей (seermak) 656 20.11.13 04:17 Сейчас в теме
(0) есть несколько пожеланий: 1. в выборку попадают не только ссылочные объекты, но и простые(типа Число, Дата и т.д.)-это плохо 2. "Не царское это дело" выбирать 3х этажную конструкцию сначала Справочник.Класс, Справочник.Объект,Справочник.Таблица - проще выбрать класс и прогнать по всему классу, а потом уже копаться в отдельно взятом (в табличную часть обработки добавить ссылку на объект)
16. Семён Павлюков (7OH) 31 20.11.13 11:05 Сейчас в теме
Претензия на универсальность была бы уместна, если бы был список метаданных (документы, справочники, регистры), в которых искать с галочками.
А искать по каждой таблице отдельно, тем более в УПП - шутка ли ?
17. Владимир Литвиненко (VladimirL) 557 20.11.13 11:21 Сейчас в теме
(16) 7OH,
Да без проблем. Подкорректировал заголовок. Слово "универсальность" относилось к алгоритму, поскольку он применим для любой таблицы базы данных. Обработка - просто пример его реализации (см. комментарий №3)
18. Роман Романов (romansun) 171 20.11.13 21:29 Сейчас в теме
выбрать *
из справочник.контрагенты
где ссылка не в (выбрать ссылка
из справочник.контрагенты)
19. Алексей Ко (Жолтокнижниг) 234 25.11.13 16:26 Сейчас в теме
Столько восхищений удивлений, а Битые ссылки. Поиск, удаление, восстановление. разве никто не видел?
20. Антон Чехов (yurets86) 26.11.13 01:48 Сейчас в теме
а разве силами самого конфигуратора не можно тоже самое сделать? я с 1с-кой только знакомлюсь и как раз только вчера вылавливал битые ссылки, и с помощью конфигуратора - отлично все получается
21. Роман Ложкин (webester) 22 28.11.13 04:20 Сейчас в теме
(20)Вы имеете ввиду ТиИ? Это не тоже самое :) У меня в базе очень часто бывает, что нет ссылок ибо РИБ и ходят не все объекты, ТиИ натворит такой бардак, что страшно подумать. Поэтому иногда удобнее бить прицельно. То есть я знаю, где ссылка реально битая, а где она заменится объектом после обмена. Еще это имеет смысл, для перерегистрации объектов. То есть в этой базе они битые, зато в другой базе живые, если это РИБ просто перезаписал найденные ссылки в другой базе, если бэкап, выгрузил и загрузил в боевую. И как бы не всегда надо их исправлять, иногда к примеру их надо просто исключить из обработки.
22. Роман Ложкин (webester) 22 28.11.13 04:23 Сейчас в теме
(19)Потому что здесь запрос?
23. Владимир Литвиненко (VladimirL) 557 28.11.13 05:32 Сейчас в теме
(22) webester,
Желтокнижник привел ссылку на очень хорошую универсальную обработку и в ней битые ссылки ищутся похожим методом. Но там не описан сам алгоритм и если спросить Яндекс или Гугл про поиск битых ссылок запросом, то на эту обработку вы не выйдите. Не вышел и я, поэтому и появилась эта публикация.
24. Алексей Ко (Жолтокнижниг) 234 28.11.13 09:24 Сейчас в теме
(22) webester, он и там(запрос), причем как мне помниться алгоритм точно такой же.
25. Роман Ложкин (webester) 22 28.11.13 09:43 Сейчас в теме
(24)ок, замечательная действительно обработка, зато здесь код сразу в статье, можно не тратить $m (если это для кого то критично), а самому собрать велосипед. По моему тоже имеет право на жизнь.
(23)либо у нас с вами гугл разный, либо вы им пользоваться не умеете, третья сверху ссылка.
26. Алексей Ко (Жолтокнижниг) 234 28.11.13 09:54 Сейчас в теме
(25) webester, у кого нет $m таких велосипедов насобирают.
27. Роман Ложкин (webester) 22 28.11.13 09:58 Сейчас в теме
(26)В целом согласен, но у человека может просто не быть здесь аккаунта. Как например у моих коллег, почему то не считают нужным, почему не знаю.
28. Владимир Литвиненко (VladimirL) 557 28.11.13 10:21 Сейчас в теме
(25) webester, Вы прям напугали картинкой. Думал инфостарт глючит :) По такому запросу будут десятки результатов. Вы забыли добавить одно ключевое слово к запросу :)
29. Роман Ложкин (webester) 22 28.11.13 10:40 Сейчас в теме
(28) я сначала посмотрел бы все варианты, а потом начал сужать круг. Но это же неважно по вашему запросу, все тоже отлично находится
30. sanek sanek_gk (sanek_gk) 64 28.11.13 12:52 Сейчас в теме
(10) TheGrr, насколько она будет читабельнее в базе 150 ГБ и более ещё неизвестно, особо когда обработка будет выполняться неопределённо долгое время и потребует неопределенное количество ресурсов для выполнения алгоритма (очень похожего на китайский)
31. sanek sanek_gk (sanek_gk) 64 28.11.13 13:01 Сейчас в теме
От себя добавлю, Афтор исправь алгоритм в обработке, будет работать неправильно при следующих условиях:
Проверяемое поле Имеет составной тип: несколько ссылочных, и один или более простых. По текущему алгоритму проверяется значение ссылки на null (наличие самой ссылки) но если там указан простой тип то её и не будет и не должно быть - однако такое значение успешно пройдёт проверку и вывалится в итоге как битая ссылка чем она в принципе не является.
32. isn Игнатьев (isn) 12 13.07.15 12:49 Сейчас в теме
Хотелось бы вариант обработки для обычного приложения а не только для управляемых форм.
33. Максим Жохов (ZhokhovM) 273 03.10.16 17:08 Сейчас в теме
Вопрос по управляемой форме, может ли эта обработка найти битые ссылка в отчетах? Например, в отборе характеристики в отчете аптеки стоит "<Объект не найден> (480:aee70015e9b8c48d11e01fecb9c33de3)".
Оставьте свое сообщение