Как удалить все элементы справочника в 1С 8.3, избегая ошибок и сохраняя целостность данных?

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

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

1. Причина ошибки "Переменная не определена (Справочники)" и ее устранение

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

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

Для этого необходимо пометить процедуру или функцию, в которой содержится обращение к объектам метаданных, специальной директивой компиляции &НаСервере (или &НаСервереБезКонтекста, если контекст формы не требуется).

Посмотрим на пример исходного кода автора и его исправление:


// Исходный код автора, который выдает ошибку на клиенте:
// Выборка = Справочники.Задачи.Выбрать; 
// Пока Выборка.Следующий Цикл 
// СправочникЗадачи = Выборка.ПолучитьОбъект; 
// СправочникЗадачи.Удалить; 
// КонецЦикла

// Исправленный код, который будет выполняться на сервере:
&НаСервере
Процедура УдалитьВсеЭлементыСправочникаНаСервере()

    // Проверяем, существует ли справочник "Задачи"
    Если Метаданные.Справочники.Найти("Задачи") = Неопределено Тогда
        Сообщить("Справочник 'Задачи' не найден в конфигурации.");
        Возврат;
    КонецЕсли;

    // Получаем менеджер справочника
    МенеджерСправочника = Справочники.Задачи;

    // Создаем выборку элементов справочника
    Выборка = МенеджерСправочника.Выбрать();

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

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

// Вызов этой процедуры на клиенте, например, по нажатию кнопки:
// &НаКлиенте
// Процедура КнопкаУдалитьВсеНажатие(Команда)
//     УдалитьВсеЭлементыСправочникаНаСервере();
// КонецПроцедуры

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

2. Куда помещать код для удаления элементов справочника?

Даже если мы исправили ошибку с серверным вызовом, возникает вопрос архитектуры: где этот код должен находиться? Как правильно указали участники форума, помещать такой код непосредственно в модуль формы элемента справочника или даже в модуль объекта справочника — это плохая практика. Почему?

  1. Модуль формы элемента: Код в модуле формы элемента предназначен для работы с *одним конкретным* элементом. Запуск цикла по *всем* элементам из формы одного элемента неочевиден и может привести к путанице.
  2. Модуль объекта справочника: Код в модуле объекта выполняется при записи, удалении или других операциях с *конкретным экземпляром* объекта. Попытка организовать массовое удаление всех объектов из модуля одного объекта нарушает его основное предназначение и может привести к рекурсивным вызовам или другим нежелательным эффектам.

Наиболее подходящие места для такого кода:

  1. Внешняя обработка: Это стандартный и наиболее гибкий способ. Вы создаете новую обработку, в ее модуле или модуле формы размещаете процедуру удаления. Это позволяет запускать обработку в любое время, передавать ей параметры (например, имя справочника), и она не привязана к конкретным объектам конфигурации. Это идеальный вариант для одноразовых или редко используемых административных задач.
  2. Модуль формы списка справочника: Если функциональность удаления всех элементов должна быть доступна прямо из формы списка справочника, то можно добавить кнопку и обработчик нажатия в модуль формы списка. Однако, даже в этом случае, сам код удаления лучше вынести в отдельный общий модуль или процедуру, чтобы не загромождать модуль формы.
  3. Общий модуль: Если функциональность массового удаления может потребоваться из разных мест (например, из нескольких обработок, или из регламентного задания), имеет смысл вынести код в общий модуль с директивами &НаСервере и &Экспорт.

3. Самый безопасный и рекомендуемый способ: Пометка на удаление

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

Наиболее безопасным и рекомендуемым способом удаления является пометка элементов на удаление с последующим использованием типовой обработки "Удаление помеченных объектов". Этот механизм встроен в платформу 1С и позволяет:

  1. Проверить ссылки: При запуске обработки "Удаление помеченных объектов" система автоматически проверяет все ссылки на помеченные объекты.
  2. Предотвратить ошибки: Если на объект есть ссылки, система не позволит его физически удалить и предложит варианты: удалить связанные объекты, отменить пометку или вручную очистить ссылки.
  3. Работать в фоновом режиме: Для больших объемов данных обработка может работать в фоновом режиме, не блокируя работу пользователей.

Для пометки всех элементов справочника на удаление мы используем свойство ПометкаУдаления:


&НаСервере
Процедура ПометитьВсеЭлементыСправочникаНаУдалениеНаСервере()

    Если Метаданные.Справочники.Найти("Задачи") = Неопределено Тогда
        Сообщить("Справочник 'Задачи' не найден в конфигурации.");
        Возврат;
    КонецЕсли;

    МенеджерСправочника = Справочники.Задачи;
    Выборка = МенеджерСправочника.Выбрать();

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

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

После выполнения этого кода необходимо запустить типовую обработку "Удаление помеченных объектов" (обычно находится в разделе "Администрирование" или "Все функции"), которая произведет физическое удаление или укажет на связанные объекты.

4. Программное физическое удаление элементов справочника

Если по какой-то причине вам необходимо произвести немедленное физическое удаление элементов справочника без использования механизма пометки на удаление, вы можете использовать метод Удалить() объекта справочника. Однако, это действие несет в себе значительные риски:

  1. Нарушение ссылочной целостности: Если на удаляемый элемент есть ссылки из других объектов, метод Удалить() вызовет ошибку, и транзакция будет отменена. Это может быть проблемой при массовом удалении, когда вы не можете заранее знать, какие элементы имеют ссылки, а какие нет.
  2. Производительность: Для каждого элемента выполняется отдельная транзакция удаления со всеми проверками и событиями объекта, что может быть очень медленно для больших объемов данных.

Чтобы ускорить процесс и избежать срабатывания некоторых проверок при физическом удалении (например, обработчиков ПередУдалением, ПриУдалении в модуле объекта), можно временно установить параметр ОбменДанными.Загрузка = Истина. Это дает системе понять, что операция выполняется в режиме загрузки данных, и некоторые проверки можно пропустить.


&НаСервере
Процедура ФизическиУдалитьВсеЭлементыСправочникаНаСервере()

    Если Метаданные.Справочники.Найти("Задачи") = Неопределено Тогда
        Сообщить("Справочник 'Задачи' не найден в конфигурации.");
        Возврат;
    КонецЕсли;

    // Сохраняем текущее значение ОбменДанными.Загрузка
    ТекущееЗначениеЗагрузка = ОбменДанными.Загрузка;
    // Устанавливаем режим загрузки для ускорения и отключения проверок
    ОбменДанными.Загрузка = Истина;

    Попытка
        МенеджерСправочника = Справочники.Задачи;
        Выборка = МенеджерСправочника.Выбрать();

        НачалоТранзакции(); // Начинаем одну большую транзакцию для всей операции

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

    Исключение
        ОтменитьТранзакцию(); // Откатываем все изменения при ошибке
        Сообщить("Произошла общая ошибка при физическом удалении: " + ОписаниеОшибки(), СтатусСообщения.Ошибка);
    КонецПопытки;

    // Восстанавливаем исходное значение ОбменДанными.Загрузка
    ОбменДанными.Загрузка = ТекущееЗначениеЗагрузка;
    Сообщить("Процесс физического удаления элементов справочника 'Задачи' завершен.");

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

Важно: Использование ОбменДанными.Загрузка = Истина требует особой осторожности. Отключаются не только обработчики событий, но и некоторые другие проверки платформы, что может привести к неконсистентным данным, если вы не полностью контролируете процесс.

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

Вопрос об удалении элементов справочника, созданного в расширении, часто возникает. Платформа 1С:Предприятие 8.3 позволяет создавать новые объекты метаданных (справочники, документы, регистры и т.д.) непосредственно в расширениях конфигурации.

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

Например, если справочник "Задачи" создан в расширении "МоеРасширение", вы все равно будете обращаться к нему как Справочники.Задачи. Платформа самостоятельно определит, что этот объект находится в расширении.

6. Методы оптимизации при удалении большого количества записей

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

  1. Фоновые задания: Запускайте процедуру удаления через фоновое задание. Это позволит избежать блокировки интерактивного сеанса пользователя и обойти ограничения по времени выполнения скриптов.
    
    // Пример вызова фонового задания
    ФоновыеЗадания.Выполнить("ИмяОбщегоМодуля.ИмяЭкспортнойПроцедурыДляУдаления", "УдалениеЗадач", ПараметрыЗадания);
    

    Где ИмяОбщегоМодуля.ИмяЭкспортнойПроцедурыДляУдаления – это экспортируемая процедура в общем модуле с директивой &НаСервере или &НаСервереБезКонтекста.

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

7. Проверка ссылочной целостности перед удалением

Как справедливо заметили в обсуждении, удалять элементы, не проверяя ссылки на них, рискованно. В 1С есть встроенный механизм для проверки ссылок на объект – это метод НайтиПоСсылкам() у объекта-ссылки.

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


&НаСервере
Процедура ПроверитьИУдалитьБезСсылокНаСервере()

    Если Метаданные.Справочники.Найти("Задачи") = Неопределено Тогда
        Сообщить("Справочник 'Задачи' не найден в конфигурации.");
        Возврат;
    КонецЕсли;

    МенеджерСправочника = Справочники.Задачи;
    Выборка = МенеджерСправочника.Выбрать();

    Пока Выборка.Следующий() Цикл
        // Получаем ссылку на элемент справочника
        СсылкаНаЭлемент = Выборка.Ссылка;

        // Ищем ссылки на данный элемент в других объектах
        МассивСсылок = СсылкаНаЭлемент.НайтиПоСсылкам();

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

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

8. Удаление иерархических справочников

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

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


&НаСервере
Процедура УдалитьИерархическийСправочникНаСервере()
    Если Метаданные.Справочники.Найти("МойИерархическийСправочник") = Неопределено Тогда
        Сообщить("Справочник 'МойИерархическийСправочник' не найден.");
        Возврат;
    КонецЕсли;

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

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

    Сообщить("Удаление иерархического справочника завершено.");
КонецПроцедуры

9. Использование типовой обработки "Групповое изменение реквизитов"

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

  1. Откройте обработку (обычно через "Администрирование" -> "Обслуживание" -> "Групповое изменение реквизитов" или "Все функции").
  2. Выберите нужный справочник (даже если он из расширения).
  3. Нажмите кнопку "Дополнительно" и установите флажок "Показывать служебные реквизиты".
  4. Найдите реквизит "ПометкаУдаления" и установите для него значение "Истина" (для пометки) или выберите действие "Удалить" (для физического удаления, если оно доступно и у вас достаточно прав).
  5. Нажмите "Изменить реквизиты". Обработка сама проведет пометку/удаление, учитывая транзакции и проверки.

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

10. Категорически не рекомендуемый способ: Удаление через SQL (TRUNCATE TABLE)

В обсуждении прозвучала идея использовать команду TRUNCATE TABLE ИмяТаблицы напрямую в СУБД (например, в MS SQL Server). Хотя это самый быстрый способ очистки таблицы, он категорически не рекомендуется для баз данных 1С по следующим причинам:

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

Не делайте этого! Всегда используйте только штатные средства платформы 1С для работы с данными.

Заключение:

Мы рассмотрели различные подходы к удалению всех элементов справочника в 1С 8.3, начиная от исправления базовой ошибки выполнения кода на клиенте и заканчивая рекомендациями по оптимизации и безопасности. Самым безопасным и рекомендуемым методом является пометка на удаление с последующей обработкой "Удаление помеченных объектов". Если требуется физическое удаление, используйте программный метод Удалить() с осторожностью, возможно, в связке с ОбменДанными.Загрузка = Истина и проверкой ссылок, а для больших объемов — через фоновые задания с порционным удалением. Категорически избегайте прямого вмешательства в базу данных через SQL.

← На главную