Как удалить запись из справочника 1С кодом: Удалить(), пометка и массовая очистка
Содержание
Удалить запись из справочника 1С программно можно двумя способами: получить объект через Ссылка.ПолучитьОбъект() и вызвать Объект.Удалить() — это физическое удаление сразу, либо вызвать Объект.ПометитьНаУдаление(Истина) — это только пометка, а сама запись остаётся в базе. Оба метода работают только на сервере.
Частое заблуждение — считать, что Удалить() проверяет, ссылаются ли на элемент документы и регистры. Не проверяет. Метод стирает запись немедленно, и в документах, где был выбран этот элемент, останутся «битые ссылки» вида <Объект не найден>. Поэтому в рабочем коде прямое удаление почти всегда идёт в паре с поиском ссылок.
Всё ниже относится к платформе 1С:Предприятие 8.3 и не зависит от конфигурации: код одинаково работает в БП 3.0, ЗУП 3.1, УТ 11 и самописных решениях.
Два базовых метода: физическое удаление и пометка
Объектная модель справочника даёт два разных инструмента. Они решают разные задачи, и путать их опасно.
Метод Удалить()
Удаляет запись из таблицы справочника без возможности отката (если не идёт транзакция). Минимальный пример для формы:
&НаКлиенте
Процедура УдалитьЭлемент(Команда)
УдалитьЭлементНаСервере(Элементы.Список.ТекущаяСтрока);
Элементы.Список.Обновить();
КонецПроцедуры
&НаСервереБезКонтекста
Процедура УдалитьЭлементНаСервере(Ссылка)
Если Не ЗначениеЗаполнено(Ссылка) Тогда
Возврат;
КонецЕсли;
Объект = Ссылка.ПолучитьОбъект();
Если Объект = Неопределено Тогда
Возврат; // запись уже удалена другим пользователем
КонецЕсли;
Объект.Удалить();
КонецПроцедуры
Что важно знать про Удалить():
- 🔹 вызывается только на сервере: у клиента нет доступа к объекту справочника;
- 🔹 выполняются обработчики модуля объекта
ПередУдалением— в них можно отменить удаление черезОтказ = Истина; - 🔹 удаление группы иерархического справочника убирает и вложенные в неё элементы;
- 🔹 записи регистров сведений, где элемент стоит в измерении со свойством
Ведущее, удаляются вместе с ним; в остальных регистрах записи останутся с битой ссылкой.
Метод ПометитьНаУдаление()
Ставит флаг ПометкаУдаления и сразу записывает объект. Запись видна в списке перечёркнутой, ссылки на неё остаются рабочими.
Объект = Ссылка.ПолучитьОбъект();
Объект.ПометитьНаУдаление(Истина); // Ложь — снять пометку
Метод вызывает запись объекта, поэтому срабатывают ПередЗаписью и ПриЗаписи. Если в этих обработчиках есть проверки заполнения или обращения к другим данным, пометка может упасть с ошибкой там, где Удалить() прошёл бы молча.
Пометка группы распространяется на вложенные элементы. Это удобно, но легко зацепить сотни записей одной строкой кода.
Пометку можно поставить и через свойство: Объект.ПометкаУдаления = Истина; Объект.Записать();. Результат тот же, но метод ПометитьНаУдаление() короче и не даёт забыть про запись.
Проверка ссылок перед удалением элемента справочника
Перед физическим удалением ищите, кто ссылается на элемент. Для этого есть глобальная функция НайтиПоСсылкам(). Она принимает массив ссылок и возвращает таблицу значений с колонками Ссылка, Данные, Метаданные.
&НаСервереБезКонтекста
Функция ЕстьСсылкиНаЭлемент(Ссылка)
МассивСсылок = Новый Массив;
МассивСсылок.Добавить(Ссылка);
ТаблицаСсылок = НайтиПоСсылкам(МассивСсылок);
Для Каждого СтрокаТЗ Из ТаблицаСсылок Цикл
Сообщить(Строка(СтрокаТЗ.Данные) + " — " + СтрокаТЗ.Метаданные.ПолноеИмя());
КонецЦикла;
Возврат ТаблицаСсылок.Количество() > 0;
КонецФункции
Колонка Данные содержит объект, где найдена ссылка: документ, другой элемент справочника или ключ записи регистра. Метаданные подскажет, в каком именно объекте конфигурации искать.
Несколько практических моментов:
- ⏱ поиск идёт по всем таблицам базы, где может храниться ссылка этого типа. На большой базе один вызов занимает секунды, поэтому передавайте массив из многих ссылок за раз, а не вызывайте функцию в цикле по одной;
- 🔁 элементы иерархического справочника ссылаются на родителя через реквизит
Родитель. Группа с детьми всегда будет «занята», пока дети существуют; - 🔗 подчинённый справочник ссылается на владельца через
Владелец. Удалить владельца, не разобравшись с подчинёнными элементами, — верный путь к мусору в базе.
Если ссылки найдены, у вас три пути: не удалять, заменить ссылки на другой элемент или пометить на удаление и оставить решение администратору.
Замена ссылок перед удалением дубля
Самый частый повод удалять элемент кодом — дубли: два «ООО Ромашка» или три одинаковые позиции номенклатуры. Тут недостаточно найти ссылки, их нужно перевести на правильный элемент, а уже потом удалять лишний.
Писать замену вручную по всем таблицам долго и рискованно: документы придётся перепроводить, наборы записей регистров — перезаписывать. В конфигурациях на БСП для этого есть готовый программный интерфейс — функция ЗаменитьСсылки общего модуля ОбщегоНазначения. Ей передают соответствие «что заменить → на что», она обходит места использования и может сразу удалить заменённые элементы.
Для пользователя тот же механизм доступен интерактивно: обработка «Поиск и удаление дублей» в разделе Администрирование → Обслуживание. Перед тем как писать свой код, проверьте её — часто её одной хватает.
Массовое удаление записей справочника кодом
Задача «почистить справочник» обычно сводится к выборке запросом и циклу. Ниже — удаление помеченных элементов справочника Номенклатура, на которые никто не ссылается. Каждый элемент обрабатывается в своём блоке Попытка, чтобы одна ошибка не остановила весь проход.
&НаСервереБезКонтекста
Процедура УдалитьПомеченнуюНоменклатуру()
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Спр
|ГДЕ
| Спр.ПометкаУдаления
| И НЕ Спр.Предопределенный";
МассивСсылок = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
Если МассивСсылок.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Ссылки, на которые кто-то ссылается, исключаем
Занятые = Новый Соответствие;
Для Каждого СтрокаТЗ Из НайтиПоСсылкам(МассивСсылок) Цикл
Занятые.Вставить(СтрокаТЗ.Ссылка, Истина);
КонецЦикла;
Удалено = 0;
Для Каждого Ссылка Из МассивСсылок Цикл
Если Занятые[Ссылка] <> Неопределено Тогда
Продолжить;
КонецЕсли;
Попытка
Объект = Ссылка.ПолучитьОбъект();
Если Объект <> Неопределено Тогда
Объект.Удалить();
Удалено = Удалено + 1;
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("Очистка номенклатуры", УровеньЖурналаРегистрации.Ошибка,,
Ссылка, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
Сообщить("Удалено элементов: " + Формат(Удалено, "ЧН=0; ЧГ=0"));
КонецПроцедуры
На что обратить внимание в этом коде:
- Предопределённые элементы отсекаются ещё в запросе. Код конфигурации обращается к ним по имени, и их исчезновение ломает типовые алгоритмы.
НайтиПоСсылкам()вызывается один раз на весь массив.- Ссылки между самими помеченными элементами тоже считаются «занятостью». Группа, все дети которой помечены, в первом проходе не удалится. Запустите процедуру повторно — после удаления детей группа освободится.
- Ошибки пишутся в журнал регистрации, а не теряются.
Перед массовым удалением в рабочей базе сделайте резервную копию. Физически удалённую запись нельзя вернуть ничем, кроме восстановления из копии.
Удаление без проверок: ОбменДанными.Загрузка
При загрузке данных или чистке тестовой базы иногда нужно удалить запись, минуя логику модуля объекта. Для этого перед вызовом ставят Объект.ОбменДанными.Загрузка = Истина.
Сама платформа обработчики при этом всё равно вызывает. Но типовые конфигурации в начале ПередУдалением и ПередЗаписью проверяют этот флаг и выходят, ничего не делая. В самописном модуле объекта такой проверки может не быть — тогда флаг ничего не даст.
Пользуйтесь этим приёмом осознанно: он отключает и полезные проверки, которые защищают учёт.
Транзакции и блокировки при удалении
Одиночный Удалить() и так выполняется в неявной транзакции. Явная транзакция нужна, когда удаление связано с другими изменениями: например, сначала заменить ссылки, потом удалить элемент. Если второй шаг упадёт, первый откатится.
Шаблон по актуальному стандарту — НачатьТранзакцию() до Попытка, управляемая блокировка первым действием:
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
ЭлементБлокировки.УстановитьЗначение("Ссылка", Ссылка);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
Объект = Ссылка.ПолучитьОбъект();
Если Объект <> Неопределено Тогда
Объект.Удалить();
КонецЕсли;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Почему блокировка до ПолучитьОбъект(): пока вы не заблокировали запись, другой сеанс может её изменить или удалить. Тогда вы удалите не ту версию данных или получите исключение в середине обработки.
Для обработок с формой есть объектная блокировка Объект.Заблокировать(). Она защищает запись от одновременного редактирования в интерфейсе, но вне транзакции не заменяет управляемую блокировку.
Размер транзакции при массовой очистке
Не заворачивайте удаление тысяч элементов в одну транзакцию. Блокировки держатся до фиксации, и всё это время пользователи, работающие с этим справочником, будут ждать или получать Конфликт блокировок при выполнении транзакции. Удаляйте порциями, например по 100 элементов, фиксируя каждую порцию отдельно.
И ещё одно правило: исключение внутри транзакции нельзя «проглотить» и продолжить. После него любая запись в той же транзакции вызовет В данной транзакции уже происходили ошибки!. Поэтому Попытка на каждый элемент, как в примере выше, допустима только когда внешней транзакции нет.
Очистка помеченных объектов: интерактивно и из кода
Помеченные записи удаляют в два шага: сначала пометка, потом отдельная процедура удаления с контролем ссылок. Так устроена штатная работа в типовых конфигурациях.
Штатная обработка
В конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11) пользователь с полными правами запускает её из раздела Администрирование → Обслуживание, группа корректировки данных, команда «Удаление помеченных объектов». Обработка сама ищет ссылки и показывает, что удалить нельзя и почему.
Регламентное задание с тем же назначением включается там же — удобно для ночной очистки.
Глобальный метод УдалитьОбъекты()
Платформа даёт метод УдалитьОбъекты(МассивОбъектов, Контроль). При Контроль = Истина он удаляет только то, на что нет ссылок вне удаляемого набора. Это удобнее ручного НайтиПоСсылкам(): взаимные ссылки внутри массива (группа и её дети) не мешают удалению.
При Контроль = Ложь метод удаляет всё переданное без проверок — по сути пакетный Удалить(). Дополнительные параметры и формат возвращаемых данных о найденных ссылках смотрите в синтакс-помощнике своей версии платформы: они менялись между релизами 8.3.
Какой метод удаления выбрать
| Метод | Когда подходит | Риски |
|---|---|---|
ПометитьНаУдаление(Истина) | Обычная работа: отказ от элемента, который мог использоваться | Почти нет: обратимо, ссылки целы |
Удалить() после НайтиПоСсылкам() | Точечная чистка, загрузка, исправление ошибок | Медленно на больших массивах |
Удалить() без проверки | Тестовые базы, заведомо неиспользуемые записи | Битые ссылки в документах и регистрах |
УдалитьОбъекты(…, Истина) | Массовая очистка помеченных из кода | Нужны права на удаление по всем объектам |
| Штатная обработка БСП | Регулярная очистка силами администратора | Длительная работа на большой базе |
Права тоже влияют на выбор. Для программного удаления пользователю нужно право Удаление на справочник. Код, который должен работать у рядового пользователя, иногда выполняют в привилегированном режиме: УстановитьПривилегированныйРежим(Истина). Делайте это только в серверном общем модуле и только для узкой, проверенной операции.
Исключения при удалении и как их разобрать
Ещё одна неочевидная ловушка — подписки на событие. Обработчик ПередУдалением может жить не в модуле объекта, а в общем модуле, привязанном через Общие → Подписки на события. Если удаление отменяется без видимой причины, проверьте подписки, у которых источником указан ваш справочник.
Частые вопросы
Можно ли восстановить элемент после Удалить()?
Почему код удаления не работает на клиенте?
&НаСервере или &НаСервереБезКонтекста, передавая туда ссылку.Попадает ли программное удаление в журнал регистрации?
Как удалить все элементы справочника в тестовой базе?
УдалитьОбъекты() с Контроль = Ложь. В рабочей базе так делать нельзя.