gifts2017

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

Опубликовал Владимир Литвиненко (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 186
.epf 11,04Kb
17.11.13
186
.epf 11,04Kb Скачать

См. также

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

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

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

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

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

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

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