Почему при поиске ссылок отображается пустой объект с GUID 0000... и как это исправить?

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

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

Разберем природу этой ошибки, выясним, где прячутся такие ссылки, и создадим алгоритм решения проблемы.

Природа "Пустого документа" в результатах поиска

Давайте проанализируем, как платформа хранит данные и почему она показывает "пустоту".

В 99% случаев, когда вы видите в результатах поиска ссылок объект вида <Объект не найден> (0:00000000000000000000000000000000) или просто пустое представление типа документа (например, "Авансовый отчет" без номера и даты), это означает следующее:

  1. Ссылка на удаляемый объект (наш Договор) находится не в другом справочнике или документе, а в записи регистра (накопления, сведений, бухгалтерии).
  2. У этой записи регистра поле "Регистратор" технически пустое. Это может быть NULL на уровне SQL или "Пустая ссылка" на уровне логики 1С.
  3. Поскольку регистры (кроме независимых регистров сведений) не являются ссылочными типами, платформа пытается показать пользователю документ, который породил эту запись (Регистратор).
  4. Так как регистратор "пустой", платформа отображает пустое представление типа документа.

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

Почему не работают стандартные средства?

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

Диагностика и поиск проблемного регистра

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

Способ 1. Использование "Инструментов разработчика" (ИР)

Если у вас есть возможность использовать внешние обработки, рекомендуем воспользоваться набором "Инструменты разработчика" (автор Сергей Старых). Это наиболее быстрый способ без написания кода.

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

Способ 2. Программный поиск "битых" движений

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

Рассмотрим алгоритм на языке 1С. Создайте внешнюю обработку, добавьте реквизит ИскомаяСсылка (любая ссылка) и выполните следующий код:


Процедура НайтиФантомныеЗаписи(ИскомаяСсылка)
    
    // Получаем метаданные конфигурации
    МетаданныеКонфигурации = Метаданные;
    
    // Список типов регистров для проверки
    МассивКоллекций = Новый Массив;
    МассивКоллекций.Добавить(МетаданныеКонфигурации.РегистрыНакопления);
    МассивКоллекций.Добавить(МетаданныеКонфигурации.РегистрыБухгалтерии);
    МассивКоллекций.Добавить(МетаданныеКонфигурации.РегистрыСведений);
    МассивКоллекций.Добавить(МетаданныеКонфигурации.РегистрыРасчета);
    
    Для Каждого Коллекция Из МассивКоллекций Цикл
        Для Каждого МетаРегистр Из Коллекция Цикл
            
            // Пропускаем независимые регистры сведений, так как у них нет регистратора (обычно)
            // Но если проблема в измерении независимого регистра - это покажет и обычный поиск.
            // Нас интересуют зависимые записи.
            
            Если МетаРегистр = Метаданные.РегистрыСведений.Найти(МетаРегистр.Имя) 
               И МетаРегистр.РежимЗаписи = РежимЗаписиРегистраСведений.Независимый Тогда
                Продолжить;
            КонецЕсли;

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

КонецПроцедуры

Как удалить найденные фантомные записи

Когда мы выяснили имя регистра (например, РегистрНакопления.Продажи), где зависла запись с пустым регистратором и ссылкой на наш Договор, нам нужно её удалить.

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

Однако, если регистратор является "Пустой ссылкой" определенного типа (как в случае автора темы — пустая ссылка на Авансовый отчет), мы можем использовать это.

Алгоритм лечения:

  1. Создаем набор записей нужного регистра.
  2. Устанавливаем отбор по регистратору, присваивая ему пустую ссылку того типа, который отображался в НайтиПоСсылкам (или который мы вычислили).
  3. Записываем пустой набор.

// Пример очистки для регистра накопления
Процедура ОчиститьБитыйРегистр(ИмяРегистра, ТипДокументаРегистратора)

    Набор = РегистрыНакопления[ИмяРегистра].СоздатьНаборЗаписей();
    
    // Создаем пустую ссылку нужного типа
    ПустаяСсылкаРегистратора = Новый(ТипДокументаРегистратора); // Например, Документы.АвансовыйОтчет.ПустаяСсылка()
    
    Набор.Отбор.Регистратор.Установить(ПустаяСсылкаРегистратора);
    
    // Считываем. Если записи есть - мы их нашли.
    Набор.Прочитать();
    Если Набор.Количество() > 0 Тогда
        Сообщить("Удаляем " + Набор.Количество() + " фантомных записей в регистре " + ИмяРегистра);
        Набор.Очистить();
        Набор.Записать(); // Записываем пустой набор, тем самым удаляя записи из БД
        Сообщить("Успешно очищено.");
    Иначе
        Сообщить("Записей с пустым регистратором этого типа не найдено.");
    КонецЕсли;

КонецПроцедуры

Решение через SQL (для клиент-серверных баз)

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

Внимание! Перед любыми действиями в SQL обязательно сделайте полную резервную копию базы данных (.bak).

  1. Узнайте имя таблицы SQL для проблемного регистра (через функцию ПолучитьСтруктуруХраненияБазыДанных() в 1С).
  2. Найдите _IDRRef (или аналогичное поле ссылки) вашего Договора в таблице базы данных.
  3. Выполните SQL-запрос для поиска записей в таблице регистра, где поле _RecorderRRef (ссылка на регистратор) заполнено нулями (0x00000000...), а в полях измерений встречается ID вашего договора.
  4. Удалите эти строки командой DELETE. Так как эти записи не привязаны к живым объектам ("сироты"), их удаление на уровне SQL обычно безопасно для целостности остальных данных.

Дополнительные рекомендации

После удаления фантомных записей обязательно выполните следующие действия:

← На главную