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

Каждому специалисту 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));
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

   

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

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

 

  

 

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

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

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

 

 

34 Comments

  1. B2B

    (0) Как ваш запрос дружит с RLS? Какова будет реакция, если в запрос попадет объект не доступный из-за RLS?

    Reply
  2. Vladimir Litvinenko

    (2) B2B,

    Цель публикации — описать алгоритм и предоставить обработку-шаблон, которую можно менять под свои нужды.

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

    Также результат запроса будет содержать данные всех записей таблицы, в которых обнаружены битые ссылки. Если цель — именно восстановление объектов, то нужно обеспечить уникальность ссылок — выгружать битые ссылки в таблицу значений и сворачивать таблицу по ним, либо модифицировать запрос c этой целью. Это уже вам решать.

    Reply
  3. yuraos

    Ценю фундаментальность!

    Особенно глобальную!!!



    Вот еще один способ, не такой глобально-фундаментальный,

    но тоже универсальный

    Как узнать, является значение ссылочного типа «битой ссылкой» или нет?

    Reply
  4. Makushimo

    Нужно ли добавлять номер строки, где есть битая ссылка, в случае перебора табличной части объекта?

    Reply
  5. Vladimir Litvinenko

    (5) Makushimo,

    Нужно ли добавлять номер строки, где есть битая ссылка, в случае перебора табличной части объекта

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

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

    Хотя замена ссылки или поиск объектов, где она была использована, обычно производятся глобально и для этого используются другие методы.

    Reply
  6. TheGrr

    Чтобы не делать трехэтажные выборки можно получить представление полей со ссылочным типом и выгрузить в ТЗ. Затем эту таблицу значений поместить во временную таблицу и вторым запросом выбрать с условием ПОДОБНО «%<Объект не найден>%».

    Reply
  7. the1

    Не хватает колонки «Ссылка», чтобы оперативно увидеть в каком документе/справочнике обнаружена битая ссылка. А так да, заслуженный плюс

    Reply
  8. Vladimir Litvinenko

    (7) TheGrr,

    Чтобы не делать трехэтажные выборки можно получить представление полей со ссылочным типом и выгрузить в ТЗ. Затем эту таблицу значений поместить во временную таблицу и вторым запросом выбрать с условием ПОДОБНО «%<Объект не найден>%».

    Ого! Или я неправильно понял, или Вы предлагаете прочитать всю таблицу и не фильтруя записи передать ее из SQL в 1С. Затем передать обратно из 1С в SQL в качестве параметра. Создать из нее временную таблицу и поместить в нее все эти данные, раздувая tempdb. Затем еще раз пробежаться по ней оператором ПОДОБНО и передать результат из SQL в 1С. Это точно оптимальный алгоритм? 🙂

    Reply
  9. TheGrr

    (9) а я не говорил, что алгоритм оптимальнее. Я говорил, что выборка будет читабельнее 😀

    Reply
  10. wunderland

    (2) B2B, Битые ссылки, обычно, интересуют тех, на кого RLS не распространяются 🙂

    Reply
  11. peterxx

    Хороший алгоритм. Плюс без разговоров.

    Reply
  12. CaSH_2004

    (0) Даже глянув наискосок видно что написано супер, уважаю такой подход, да и тема актуальная

    Reply
  13. seermak

    (0) есть несколько пожеланий: 1. в выборку попадают не только ссылочные объекты, но и простые(типа Число, Дата и т.д.)-это плохо 2. «Не царское это дело» выбирать 3х этажную конструкцию сначала Справочник.Класс, Справочник.Объект,Справочник.Таблица — проще выбрать класс и прогнать по всему классу, а потом уже копаться в отдельно взятом (в табличную часть обработки добавить ссылку на объект)

    Reply
  14. 7OH

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

    А искать по каждой таблице отдельно, тем более в УПП — шутка ли ?

    Reply
  15. Vladimir Litvinenko

    (16) 7OH,

    Да без проблем. Подкорректировал заголовок. Слово «универсальность» относилось к алгоритму, поскольку он применим для любой таблицы базы данных. Обработка — просто пример его реализации (см. комментарий №3)

    Reply
  16. romansun

    выбрать *

    из справочник.контрагенты

    где ссылка не в (выбрать ссылка

    из справочник.контрагенты)

    Reply
  17. Жолтокнижниг

    Столько восхищений удивлений, а Битые ссылки. Поиск, удаление, восстановление. разве никто не видел?

    Reply
  18. yurets86

    а разве силами самого конфигуратора не можно тоже самое сделать? я с 1с-кой только знакомлюсь и как раз только вчера вылавливал битые ссылки, и с помощью конфигуратора — отлично все получается

    Reply
  19. webester

    (20)Вы имеете ввиду ТиИ? Это не тоже самое 🙂 У меня в базе очень часто бывает, что нет ссылок ибо РИБ и ходят не все объекты, ТиИ натворит такой бардак, что страшно подумать. Поэтому иногда удобнее бить прицельно. То есть я знаю, где ссылка реально битая, а где она заменится объектом после обмена. Еще это имеет смысл, для перерегистрации объектов. То есть в этой базе они битые, зато в другой базе живые, если это РИБ просто перезаписал найденные ссылки в другой базе, если бэкап, выгрузил и загрузил в боевую. И как бы не всегда надо их исправлять, иногда к примеру их надо просто исключить из обработки.

    Reply
  20. webester

    (19)Потому что здесь запрос?

    Reply
  21. Vladimir Litvinenko

    (22) webester,

    Желтокнижник привел ссылку на очень хорошую универсальную обработку и в ней битые ссылки ищутся похожим методом. Но там не описан сам алгоритм и если спросить Яндекс или Гугл про поиск битых ссылок запросом, то на эту обработку вы не выйдите. Не вышел и я, поэтому и появилась эта публикация.

    Reply
  22. Жолтокнижниг

    (22) webester, он и там(запрос), причем как мне помниться алгоритм точно такой же.

    Reply
  23. webester

    (24)ок, замечательная действительно обработка, зато здесь код сразу в статье, можно не тратить $m (если это для кого то критично), а самому собрать велосипед. По моему тоже имеет право на жизнь.

    (23)либо у нас с вами гугл разный, либо вы им пользоваться не умеете, третья сверху ссылка.

    Reply
  24. Жолтокнижниг

    (25) webester, у кого нет $m таких велосипедов насобирают.

    Reply
  25. webester

    (26)В целом согласен, но у человека может просто не быть здесь аккаунта. Как например у моих коллег, почему то не считают нужным, почему не знаю.

    Reply
  26. Vladimir Litvinenko

    (25) webester, Вы прям напугали картинкой. Думал инфостарт глючит 🙂 По такому запросу будут десятки результатов. Вы забыли добавить одно ключевое слово к запросу 🙂

    Reply
  27. webester

    (28) я сначала посмотрел бы все варианты, а потом начал сужать круг. Но это же неважно по вашему запросу, все тоже отлично находится

    Reply
  28. sanek_gk

    (10) TheGrr, насколько она будет читабельнее в базе 150 ГБ и более ещё неизвестно, особо когда обработка будет выполняться неопределённо долгое время и потребует неопределенное количество ресурсов для выполнения алгоритма (очень похожего на китайский)

    Reply
  29. sanek_gk

    От себя добавлю, Афтор исправь алгоритм в обработке, будет работать неправильно при следующих условиях:

    Проверяемое поле Имеет составной тип: несколько ссылочных, и один или более простых. По текущему алгоритму проверяется значение ссылки на null (наличие самой ссылки) но если там указан простой тип то её и не будет и не должно быть — однако такое значение успешно пройдёт проверку и вывалится в итоге как битая ссылка чем она в принципе не является.

    Reply
  30. isn

    Хотелось бы вариант обработки для обычного приложения а не только для управляемых форм.

    Reply
  31. ZhokhovM

    Вопрос по управляемой форме, может ли эта обработка найти битые ссылка в отчетах? Например, в отборе характеристики в отчете аптеки стоит «<Объект не найден> (480:aee70015e9b8c48d11e01fecb9c33de3)».

    Reply
  32. docerman

    А где-же в результате сами объекты в которых есть битые ссылки?

    Reply
  33. EvgenURNN

    Такой алгоритм не будет работать на большой базе, где есть реквизит типа «Любая ссылка» и много-много разных ссылочных таблиц. Придется:

    1. разбивать на подзапросы

    2. делать явное левое соединение либо ВЫРАЗИТЬ

    Reply
  34. fieryfist

    (34)Сами объекты можно найти например обработкой Универсальный подбор и обработка объектов (через поиск ссылок на объект). Знаю, что битую ссылку не добавить для поиска, но немного доработав обработку, это можно сделать.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *