Как найти документы, существующие в одной базе 1С, но отсутствующие в другой, и выявить битые ссылки?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Задача выявления битых ссылок в информационной базе 1С, особенно после операций удаления или некорректного обмена данными, является критически важной для поддержания целостности и работоспособности системы. Когда документы, на которые ссылаются другие объекты, были удалены из одной базы, но не из другой, или были удалены по ошибке, это приводит к так называемым «битым ссылкам». Рассмотрим различные подходы и алгоритмы для решения этой проблемы, начиная от типовых средств 1С до программных методов и прямого взаимодействия с СУБД.

Определение проблемы: почему возникают битые ссылки?

Битые ссылки могут возникать по ряду причин:

  1. Некорректное удаление: Объекты были помечены на удаление, но не были удалены корректно (например, без проверки ссылочной целостности), или были удалены напрямую средствами SQL.
  2. Ошибки обмена данными: При синхронизации между базами один из объектов был удален в базе-источнике, но остался в базе-приемнике, или наоборот.
  3. Ручные корректировки: Изменения данных напрямую в СУБД без использования механизмов 1С.
  4. Нехватка ресурсов: Типовые обработки проверки ссылочной целостности могут не справляться с большим объемом данных, вылетая с ошибками нехватки памяти, что мешает обнаружению и исправлению проблем.

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

Метод 1: Сравнение через COM-соединение

Один из наиболее распространенных способов для сравнения данных между двумя базами 1С — это использование COM-соединения. Этот метод позволяет одной базе выступать в роли клиента и подключаться к другой базе как к серверу, получая доступ к ее данным.

  1. Установим COM-соединение:

    Для начала нам потребуется установить COM-соединение с удаленной базой. Для этого используем объект COMОбъект. Убедитесь, что на сервере 1С (или на клиентской машине, если это файловая база) зарегистрирован COM-сервер 1С (обычно V83.COMConnector для версии 8.3).

    
    Попытка
        ComСоединение = Новый COMОбъект("V83.COMConnector");
        // Строка подключения для клиент-серверной базы
        СтрокаПодключения = "Srvr='ИМЯ_СЕРВЕРА'; Ref='ИМЯ_БАЗЫ'; Usr='ПОЛЬЗОВАТЕЛЬ'; Pwd='ПАРОЛЬ';";
        // Строка подключения для файловой базы
        // СтрокаПодключения = "File=""C:\Путь\К\Базе""; Usr=""ПОЛЬЗОВАТЕЛЬ""; Pwd=""ПАРОЛЬ"";";
                
        ВнешняяБаза = ComСоединение.Connect(СтрокаПодключения);
        Сообщить("Соединение с внешней базой установлено успешно.");
    Исключение
        Сообщить("Ошибка подключения к внешней базе: " + ОписаниеОшибки());
        Возврат;
    КонецПопытки;
    

    Обратите внимание на синтаксис строки подключения, он зависит от типа базы (файловая или клиент-серверная).

  2. Получим УИДы из обеих баз:

    После успешного подключения мы можем получить УИДы (уникальные идентификаторы) всех документов определенного типа из обеих баз. Допустим, мы ищем отсутствующие Документ.РеализацияТоваровУслуг. Нам необходимо собрать УИДы из текущей базы и из внешней.

    Сначала получим УИДы из текущей базы:

    
    ЗапросТекущаяБаза = Новый Запрос;
    ЗапросТекущаяБаза.Текст = "ВЫБРАТЬ УИД(Ссылка) КАК УИДДокумента ИЗ Документ.РеализацияТоваровУслуг";
    ТаблицаУИДТекущейБазы = ЗапросТекущаяБаза.Выполнить().Выгрузить();
    НаборУИДТекущейБазы = Новый Соответствие;
    Для Каждого СтрокаУИД Из ТаблицаУИДТекущейБазы Цикл
        НаборУИДТекущейБазы.Вставить(СтрокаУИД.УИДДокумента, Истина);
    КонецЦикла;
    Сообщить("Получено " + НаборУИДТекущейБазы.Количество() + " УИДов из текущей базы.");
    

    Затем получим УИДы из внешней базы через COM-соединение. Для этого мы используем объект Запрос из подключенной внешней базы.

    
    ЗапросВнешняяБаза = ВнешняяБаза.NewObject("Запрос");
    ЗапросВнешняяБаза.Текст = "ВЫБРАТЬ УИД(Ссылка) КАК УИДДокумента ИЗ Документ.РеализацияТоваровУслуг";
    ТаблицаУИДВнешнейБазы = ЗапросВнешняяБаза.Выполнить().Выгрузить();
    НаборУИДВнешнейБазы = Новый Соответствие;
    Для Каждого СтрокаУИД Из ТаблицаУИДВнешнейБазы Цикл
        НаборУИДВнешнейБазы.Вставить(СтрокаУИД.УИДДокумента, Истина);
    КонецЦикла;
    Сообщить("Получено " + НаборУИДВнешнейБазы.Количество() + " УИДов из внешней базы.");
    
  3. Сравним наборы УИДов:

    Теперь, когда у нас есть два набора УИДов, мы можем найти те УИДы, которые присутствуют в одной базе, но отсутствуют в другой.

    Поиск документов, которые есть в текущей базе, но отсутствуют во внешней:

    
    Сообщить("--- Документы, существующие в текущей базе, но отсутствующие во внешней: ---");
    Для Каждого ЭлементНабора Из НаборУИДТекущейБазы Цикл
        Если НЕ НаборУИДВнешнейБазы.Найти(ЭлементНабора.Ключ) Тогда
            Попытка
                // Для получения ссылки по УИД используем метод УникальныйИдентификатор.НайтиПоУникальномуИдентификатору()
                СсылкаНаДокумент = УникальныйИдентификатор(ЭлементНабора.Ключ).НайтиПоУникальномуИдентификатору();
                Если СсылкаНаДокумент.Пустая() Тогда
                    Сообщить("Объект с УИД " + ЭлементНабора.Ключ + " не найден в текущей базе по ссылке, возможно, он уже битый или удален.");
                Иначе
                    Сообщить("Документ: " + СсылкаНаДокумент + " (УИД: " + ЭлементНабора.Ключ + ")");
                    // Здесь можно добавить логику для исправления или пометки битой ссылки
                    // Например, пометить на удаление, если он действительно лишний в текущей базе
                    // Или зафиксировать в каком-либо служебном регистре для дальнейшего анализа
                КонецЕсли;
            Исключение
                Сообщить("Ошибка при обработке УИД " + ЭлементНабора.Ключ + ": " + ОписаниеОшибки());
            КонецПопытки;
        КонецЕсли;
    КонецЦикла;
    

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

    
    Сообщить("--- Документы, существующие во внешней базе, но отсутствующие в текущей: ---");
    Для Каждого ЭлементНабора Из НаборУИДВнешнейБазы Цикл
        Если НЕ НаборУИДТекущейБазы.Найти(ЭлементНабора.Ключ) Тогда
            Сообщить("УИД: " + ЭлементНабора.Ключ + " отсутствует в текущей базе. (Возможно, потребуется восстановить из внешней базы)");
            // Здесь мы не можем получить ссылку из текущей базы по УИД, так как ее там нет.
            // Можно записать этот УИД для последующей загрузки или создания объекта.
        КонецЕсли;
    КонецЦикла;
    
  4. Обобщение для разных типов документов:

    Если видов документов, подлежащих сравнению, много, рекомендуем динамически формировать текст запроса, перебирая метаданные программно. Это позволит автоматизировать процесс для всех необходимых типов объектов, а не писать запросы вручную для каждого из них.

Метод 2: Использование HTTP-сервиса для сравнения УИДов

Для более распределенных или веб-ориентированных сценариев, а также для обхода некоторых ограничений COM-соединения (например, необходимость регистрации COM-коннектора, особенности работы на Linux-серверах), можно использовать HTTP-сервис. Этот метод требует создания HTTP-сервиса в одной из баз, который будет отдавать список УИДов.

  1. Создание HTTP-сервиса:

    В одной из баз (например, в той, из которой мы хотим получить список документов, назовем ее «серверной» базой) создаем HTTP-сервис. Он будет принимать запросы и возвращать список УИДов документов определенного типа (или всех).

    Для этого в конфигурации создаем новый объект метаданных "HTTP-сервис", добавляем в него шаблон URL (например, /DocumentGUIDs) и метод (например, GET). В модуле этого HTTP-сервиса реализуем экспортную функцию.

    Пример обработчика HTTP-сервиса, который возвращает УИДы документов "РеализацияТоваровУслуг":

    
    // В модуле HTTP-сервиса, например, в публичном методе GetDocumentGUIDs
    // Имя метода должно соответствовать настроенному в HTTP-сервисе
    Функция GetDocumentGUIDs(ЗапросHttpСервиса) Экспорт
        // Пример: получаем тип документа из параметров запроса
        ТипДокументаСтрока = ЗапросHttpСервиса.ПараметрыЗапроса.Получить("ТипДокумента");
        Если Не ЗначениеЗаполнено(ТипДокументаСтрока) Тогда
            ТипДокументаСтрока = "Документ.РеализацияТоваровУслуг"; // Тип по умолчанию
        КонецЕсли;
    
        Запрос = Новый Запрос;
        Запрос.Текст = "ВЫБРАТЬ УИД(Ссылка) КАК УИДДокумента ИЗ " + ТипДокументаСтрока;
        РезультатЗапроса = Запрос.Выполнить().Выгрузить();
    
        МассивУИДов = Новый Массив;
        Для Каждого Строка Из РезультатЗапроса Цикл
            МассивУИДов.Добавить(Строка.УИДДокумента.Строка()); // Преобразуем УИД в строку для сериализации
        КонецЦикла;
    
        ОтветHttpСервиса = Новый Соответствие;
        ОтветHttpСервиса.Вставить("УИДы", МассивУИДов);
    
        // Сериализуем результат в JSON или XDTO
        ЗаписьJSON = Новый ЗаписьJSON;
        ЗаписьJSON.УстановитьСтроку();
        ЗаписатьJSON(ЗаписьJSON, ОтветHttpСервиса);
        РезультатСтрока = ЗаписьJSON.Закрыть();
    
        Ответ = Новый HTTPСервисОтвет(200);
        Ответ.УстановитьТелоИзСтроки(РезультатСтрока, КодировкаТекста.UTF8);
        Ответ.Заголовки.Вставить("Content-Type", "application/json");
        Возврат Ответ;
    КонецФункции
    

    После создания и публикации HTTP-сервиса, убедитесь, что он доступен по сети.

  2. Вызов HTTP-сервиса из другой базы:

    Из второй базы (клиентской) мы делаем HTTP-запрос к созданному сервису, получаем список УИДов и сравниваем его со списком УИДов текущей базы, как мы делали это в случае с COM-соединением.

    
    // В клиентской базе
    АдресСервиса = "http://localhost/МояБазаСервер/hs/МойСервис/DocumentGUIDs?ТипДокумента=Документ.РеализацияТоваровУслуг"; // Пример адреса и параметра
            
    Попытка
        HTTPЗапрос = Новый HTTPЗапрос(АдресСервиса);
        HTTPСоединение = Новый HTTPСоединение("localhost", , , , , 60); // Адрес сервера, тайм-аут 60 секунд
    
        HTTPОтвет = HTTPСоединение.Отправить(HTTPЗапрос);
    
        Если HTTPОтвет.КодСостояния = 200 Тогда
            СтрокаJSON = HTTPОтвет.ПолучитьТелоКакСтроку();
            ЧтениеJSON = Новый ЧтениеJSON;
            ЧтениеJSON.УстановитьСтроку(СтрокаJSON);
            РезультатСервиса = ПрочитатьJSON(ЧтениеJSON);
            ЧтениеJSON.Закрыть();
    
            МассивУИДовВнешнейБазыСтрока = РезультатСервиса.УИДы;
    
            НаборУИДВнешнейБазы = Новый Соответствие;
            Для Каждого УИДСтрока Из МассивУИДовВнешнейБазыСтрока Цикл
                НаборУИДВнешнейБазы.Вставить(Новый УникальныйИдентификатор(УИДСтрока), Истина); // Преобразуем строку обратно в УИД
            КонецЦикла;
    
            Сообщить("Получено " + НаборУИДВнешнейБазы.Количество() + " УИДов из внешней базы через HTTP-сервис.");
            // Далее сравнение как в Методе 1 с использованием НаборУИДВнешнейБазы
        Иначе
            Сообщить("Ошибка вызова HTTP-сервиса: " + HTTPОтвет.КодСостояния + " " + HTTPОтвет.ПолучитьТелоКакСтроку());
        КонецЕсли;
    Исключение
        Сообщить("Ошибка при работе с HTTP-сервисом: " + ОписаниеОшибки());
    КонецПопытки;
    

    Данный подход позволяет гибко настраивать запросы и обмениваться данными даже между базами, находящимися на разных платформах или не имеющими прямого сетевого доступа для COM.

Метод 3: Прямое сравнение данных в SQL

Если обе информационные базы 1С находятся на одном и том же экземпляре SQL-сервера, вы можете использовать возможности СУБД для прямого сравнения данных. Этот метод часто является самым быстрым и эффективным для очень больших объемов данных, поскольку перекладывает всю нагрузку на базу данных.

  1. Подготовка и понимание структуры 1С в SQL:

    Убедитесь, что у вас есть права на чтение данных из обеих баз на уровне SQL. 1С хранит ссылки на объекты в виде УИДов в соответствующих таблицах. Каждому типу документа или справочника в 1С соответствует своя таблица в SQL, имя которой обычно имеет формат _Document<NN> или _Reference<NN>, где <NN> — внутренний ID типа объекта. УИДы объектов хранятся в поле _IDRRef (для ссылок) или _ID (для самого объекта). Мы будем работать с полем _IDRRef, которое содержит бинарное представление УИДа.

    Вы можете узнать внутренние имена таблиц SQL для объектов 1С, например, используя "Консоль запросов" в режиме отладки или анализируя структуру базы данных.

  2. Сравнение с помощью LEFT JOIN:

    Для поиска документов, которые есть в одной базе (например, База1), но нет в другой (База2), используем оператор LEFT JOIN. Вам нужно знать имена таблиц документов в SQL.

    Предположим, что Документ.РеализацияТоваровУслуг соответствует таблице _DocumentNN (где NN - реальный номер) в обеих базах.

    
    SELECT
        CAST(T1._IDRRef AS UNIQUEIDENTIFIER) AS GUID_Base1,
        T1._Number AS DocumentNumber, -- Пример поля для дополнительной информации
        T1._Date AS DocumentDate
    FROM
        [ИмяБазы1].dbo._DocumentNN AS T1 -- Замените 'ИмяБазы1' и 'NN' на реальные значения
    LEFT JOIN
        [ИмяБазы2].dbo._DocumentNN AS T2 ON T1._IDRRef = T2._IDRRef
    WHERE
        T2._IDRRef IS NULL;
    

    Этот запрос вернет УИДы документов (преобразованные в UNIQUEIDENTIFIER для удобства чтения) из База1, для которых нет соответствующих записей в База2. Вы можете добавить другие поля из T1 для получения дополнительной информации о найденных отсутствующих документах.

    Аналогично, чтобы найти документы, которые есть во Базе2, но отсутствуют во Базе1, достаточно поменять местами T1 и T2 в LEFT JOIN и WHERE.

  3. Дополнительные аспекты SQL-сравнения:
    • Индексы: Крайне важно убедиться, что по полю _IDRRef (или _ID) существуют индексы в обеих таблицах для ускорения сравнения. Без индексов производительность может быть очень низкой.
    • Масштабируемость: Для очень больших баз может потребоваться пакетная обработка или использование временных таблиц для выгрузки УИДов перед выполнением основного соединения, чтобы снизить нагрузку на память SQL-сервера.
    • Динамическое определение таблиц: Для динамического определения имен таблиц 1С и соответствующих им SQL-таблиц можно использовать системные представления SQL (например, sys.tables, sys.columns) или заранее сформировать соответствие по метаданным 1С.
    • Безопасность: Прямая работа с SQL требует особой осторожности и глубокого понимания структуры 1С, чтобы избежать повреждения данных. Всегда работайте с копиями баз и проводите тестирование.

Метод 4: Использование типовых механизмов 1С и внешних обработок

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

  1. "Тестирование и исправление информационной базы":

    Эта встроенная функция (доступна через меню "Администрирование" -> "Обслуживание" -> "Тестирование и исправление") включает опцию "Проверка ссылочной целостности". Она предназначена для обнаружения битых ссылок внутри одной базы. Однако, как отмечает автор исходного сообщения, для больших объемов данных эта обработка может вызывать ошибки нехватки памяти, делая ее непригодной.

  2. "Инструменты разработчика" и "Поиск битых ссылок":

    Подсистема "Инструменты разработчика" в 1С (присутствует во многих современных конфигурациях) содержит мощный инструмент "Поиск битых ссылок". Он более гибок и оптимизирован по сравнению с типовым тестированием и позволяет:

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

    Результаты можно просматривать в "Консоли запросов" и "Редакторе объекта БД". Этот инструмент является более предпочтительным, чем стандартное тестирование, для средних и больших баз, но при экстремально больших объемах данных все еще может столкнуться с ограничениями по памяти.

  3. "Консоль сравнения данных из разных источников" (КСД) и аналогичные обработки:

    Существуют мощные внешние обработки, например, "Консоль сравнения данных из разных источников" (КСД), которые позволяют сравнивать данные из различных источников. Они поддерживают подключение к другим информационным базам 1С (через COM-соединение), а также напрямую к базам данных SQL. Эти обработки предоставляют удобный графический интерфейс для:

    • Настройки запросов к разным источникам.
    • Соединения результатов запросов по простому или составному ключу (например, по УИДам).
    • Фильтрации и визуализации расхождений.

    Подобные инструменты существенно упрощают процесс сравнения, абстрагируясь от низкоуровневой работы с COM или SQL-запросами, и могут быть отличным решением для тех, кто не готов писать собственный код. Также существуют специализированные обработки для сверки документов и проводок, доступные на различных ресурсах для 1С-разработчиков (например, Инфостарт), которые могут включать сравнение по контрольным суммам, статусу проведения, датам и другим ключевым реквизитам.

  4. Журнал регистрации при синхронизации:

    При использовании механизмов синхронизации данных (РИБ, обмен по правилам через "Универсальный формат Enterprise Data") внимательно анализируйте журнал регистрации. Ошибки и предупреждения в нем часто указывают на проблемные ситуации, такие как непроведенные документы, незаполненные реквизиты или конфликты УИДов, что может быть причиной отсутствия документов или появления битых ссылок. Регулярный просмотр журнала регистрации при каждом обмене помогает своевременно выявлять и устранять проблемы.

  5. Полнотекстовый поиск:

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

Программный поиск битых ссылок запросом

Если типовые средства не справляются с объемом данных, а внешние обработки не подходят, можно реализовать универсальный алгоритм поиска битых ссылок с помощью запросов к БД. Этот метод считается более быстрым по сравнению с обходом выборки в цикле, поскольку перекладывает основную работу на СУБД. Мы будем динамически формировать запрос, который пройдется по всем ссылочным полям всех таблиц и проверит существование объекта-владельца ссылки.

  1. Идентификация ссылочных полей:

    Нам необходимо получить список всех реквизитов, имеющих тип "Ссылка на объект". Это можно сделать, обратившись к метаданным. Мы переберем все объекты, которые могут иметь ссылочные поля (документы, справочники, регистры, планы видов характеристик и т.д.), и соберем информацию об их реквизитах и реквизитах табличных частей.

    
    МассивТаблицИПолейДляПроверки = Новый Массив;
    
    // Перебираем документы
    Для Каждого МДДокумент Из Метаданные.Документы Цикл
        // Реквизиты документа
        Для Каждого Реквизит Из МДДокумент.Реквизиты Цикл
            Если Реквизит.Тип.Ссылки().Количество() > 0 Тогда // Проверяем, что тип содержит ссылки
                МассивТаблицИПолейДляПроверки.Добавить(Новый Структура("ОбъектМетаданных", МДДокумент, "Поле", Реквизит));
            КонецЕсли;
        КонецЦикла;
        // Табличные части документа
        Для Каждого ТабличнаяЧасть Из МДДокумент.ТабличныеЧасти Цикл
            Для Каждого РеквизитТЧ Из ТабличнаяЧасть.Реквизиты Цикл
                Если РеквизитТЧ.Тип.Ссылки().Количество() > 0 Тогда
                    МассивТаблицИПолейДляПроверки.Добавить(Новый Структура("ОбъектМетаданных", ТабличнаяЧасть, "Поле", РеквизитТЧ));
                КонецЕсли;
            КонецЦикла;
        КонецЦикла;
    КонецЦикла;
    
    // По аналогии, перебираем справочники, регистры сведений, регистры накопления и т.д.
    Для Каждого МДСправочник Из Метаданные.Справочники Цикл
        // ... (аналогичный код для реквизитов и табличных частей)
    КонецЦикла;
    
    // Для Регистров сведений
    Для Каждого МДРегистрСведений Из Метаданные.РегистрыСведений Цикл
        Для Каждого Измерение Из МДРегистрСведений.Измерения Цикл
            Если Измерение.Тип.Ссылки().Количество() > 0 Тогда
                МассивТаблицИПолейДляПроверки.Добавить(Новый Структура("ОбъектМетаданных", МДРегистрСведений, "Поле", Измерение));
            КонецЕсли;
        КонецЦикла;
        Для Каждого Реквизит Из МДРегистрСведений.Реквизиты Цикл
            Если Реквизит.Тип.Ссылки().Количество() > 0 Тогда
                МассивТаблицИПолейДляПроверки.Добавить(Новый Структура("ОбъектМетаданных", МДРегистрСведений, "Поле", Реквизит));
            КонецЕсли;
        КонецЦикла;
    КонецЦикла;
    
  2. Формирование и выполнение запроса:

    После сбора информации о ссылочных полях, мы можем сформировать составной запрос, используя ЛЕВОЕ СОЕДИНЕНИЕ для каждого ссылочного поля с таблицей, на которую оно ссылается. Если в результате соединения ссылка не будет найдена (то есть соответствующее поле из правой таблицы соединения будет NULL), это будет означать битую ссылку.

    Этот подход позволяет избежать загрузки всех данных в память 1С и перекладывает основную нагрузку на СУБД. Вам потребуется построить сложный динамический запрос, который может состоять из множества запросов, объединенных оператором ОБЪЕДИНИТЬ ВСЕ, для каждого найденного ссылочного поля.

    Пример (упрощенный, для одного ссылочного поля в документе):

    
    ТекстЗапроса = Новый Строка;
    МассивФрагментовЗапроса = Новый Массив;
    
    // Для каждого элемента из МассивТаблицИПолейДляПроверки формируем фрагмент запроса
    Для Каждого ЭлементПроверки Из МассивТаблицИПолейДляПроверки Цикл
        ИмяОсновногоОбъекта = ЭлементПроверки.ОбъектМетаданных.ПолноеИмя();
        ИмяСсылочногоПоля = ЭлементПроверки.Поле.Имя;
        ИмяСсылочногоТипа = ЭлементПроверки.Поле.Тип.ПривестиКТипу("СправочникСсылка") ИЛИ ЭлементПроверки.Поле.Тип.ПривестиКТипу("ДокументСсылка");
                
        // Если тип составной, нужно будет обрабатывать каждый возможный ссылочный тип
        // Здесь для простоты предполагаем один ссылочный тип
        Если ИмяСсылочногоТипа <> Неопределено Тогда
            ФрагментЗапроса = "
            |ВЫБРАТЬ РАЗЛИЧНЫЕ
            |    ОсновнойОбъект.Ссылка КАК СсылающийсяОбъект,
            |    ОсновнойОбъект." + ИмяСсылочногоПоля + " КАК БитыйЭлемент,
            |    &ПредставлениеСсылки КАК ПредставлениеБитойСсылки,
            |    &ТипСсылающегосяОбъекта КАК ТипСсылающегосяОбъекта,
            |    &ИмяСсылочногоПоля КАК ИмяСсылочногоПоля
            |ИЗ
            |    " + ИмяОсновногоОбъекта + " КАК ОсновнойОбъект
            |        ЛЕВОЕ СОЕДИНЕНИЕ " + ИмяСсылочногоТипа + " КАК СсылочныйОбъект
            |        ПО ОсновнойОбъект." + ИмяСсылочногоПоля + " = СсылочныйОбъект.Ссылка
            |ГДЕ
            |    ОсновнойОбъект." + ИмяСсылочногоПоля + " ЕСТЬ НЕ NULL
            |    И СсылочныйОбъект.Ссылка ЕСТЬ NULL
            |";
            МассивФрагментовЗапроса.Добавить(ФрагментЗапроса);
        КонецЕсли;
    КонецЦикла;
    
    Если МассивФрагментовЗапроса.Количество() > 0 Тогда
        ТекстЗапроса = СтрСоединить(МассивФрагментовЗапроса, "
        |ОБЪЕДИНИТЬ ВСЕ
        |");
        Запрос = Новый Запрос(ТекстЗапроса);
        // Установка параметров для каждого типа ссылки, чтобы получить их представление
        Запрос.УстановитьПараметр("ПредставлениеСсылки", "Неизвестный объект"); // Или получить более точное представление
        Запрос.УстановитьПараметр("ТипСсылающегосяОбъекта", ""); // Заполнить динамически
        Запрос.УстановитьПараметр("ИмяСсылочногоПоля", ""); // Заполнить динамически
    
        Результат = Запрос.Выполнить().Выгрузить();
        Сообщить("Найдено битых ссылок: " + Результат.Количество());
        Для Каждого СтрокаРезультата Из Результат Цикл
            Сообщить("Объект: " + СтрокаРезультата.СсылающийсяОбъект + 
                     " содержит битую ссылку в поле '" + СтрокаРезультата.ИмяСсылочногоПоля + 
                     "' на объект: " + СтрокаРезультата.БитыйЭлемент + 
                     " (" + СтрокаРезультата.ПредставлениеБитойСсылки + ")");
        КонецЦикла;
    КонецЕсли;
    

    Динамическое формирование такого запроса для всех ссылочных полей является достаточно сложной задачей, но оно позволит эффективно выявить битые ссылки, не загружая в память всю базу, а используя мощности СУБД. При этом необходимо аккуратно работать с представлением ссылочных типов и параметров.

Предотвращение появления битых ссылок

Лучший способ борьбы с битыми ссылками — это их предотвращение. Рассмотрим ключевые моменты, которые помогут поддерживать ссылочную целостность в вашей системе 1С:

  1. Используйте стандартные механизмы удаления:

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

    
    // Пример безопасного программного удаления
    ДокументСсылка = Документы.МойДокумент.НайтиПоНомеру("000001", Дата(2023, 1, 1));
    Если ДокументСсылка <> Неопределено Тогда
        Попытка
            // Пометка на удаление
            ОбъектДокумента = ДокументСсылка.ПолучитьОбъект();
            ОбъектДокумента.УстановитьПометкуУдаления(Истина);
            ОбъектДокумента.Записать();
            Сообщить("Документ " + ДокументСсылка + " помечен на удаление.");
                    
            // Или полное удаление с проверкой ссылок (если необходимо)
            // УдалитьОбъекты(МассивИзОдномерногоЗначения(ДокументСсылка));
        Исключение
            Сообщить("Не удалось пометить на удаление документ " + ДокументСсылка + ": " + ОписаниеОшибки());
        КонецПопытки;
    КонецЕсли;
    
  2. Корректная настройка прав пользователей:

    Ограничьте права пользователей на прямое удаление объектов. Предоставляйте права только на пометку на удаление. Окончательное удаление должно проводиться администратором или ответственным лицом после проведения всех необходимых проверок ссылочной целостности и подтверждения, что объект действительно не нужен.

  3. Контроль при обмене данными:

    Тщательно настраивайте правила обмена данными, особенно при выгрузке/загрузке объектов. Убедитесь, что логика обработки удаленных объектов в обеих базах корректна и не приводит к появлению "призрачных" ссылок или объектов, которые ссылаются на несуществующие данные.

  4. Регистр "неподлежащие удалению":

    Идея автора исходного сообщения о создании регистра документов, не подлежащих удалению, является хорошей превентивной мерой. Мы можем использовать такой регистр для дополнительной проверки перед попыткой удаления, но он не отменяет необходимости стандартной проверки ссылочной целостности, а дополняет ее бизнес-логикой.

  5. Регулярное обслуживание ИБ:

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

Заключение

Выбор алгоритма для поиска документов, отсутствующих в одной базе, зависит от специфики вашей инфраструктуры, объема данных и доступных ресурсов. Для небольших баз и редких проверок можно использовать типовые механизмы 1С или COM-соединение. Для больших объемов и регулярных проверок предпочтительнее HTTP-сервисы или прямое взаимодействие с SQL-сервером, поскольку они позволяют переложить основную нагрузку на СУБД. Главное — это комплексный подход, который сочетает как активный поиск и исправление проблем, так и превентивные меры для предотвращения их возникновения. Такой подход поможет поддерживать вашу информационную базу 1С в актуальном и целостном состоянии.

← На главную