Как удалить запись из справочника 1С кодом: Удалить(), пометка и массовая очистка

· 9 мин

Содержание

Удалить запись из справочника 1С программно можно двумя способами: получить объект через Ссылка.ПолучитьОбъект() и вызвать Объект.Удалить() — это физическое удаление сразу, либо вызвать Объект.ПометитьНаУдаление(Истина) — это только пометка, а сама запись остаётся в базе. Оба метода работают только на сервере.

Частое заблуждение — считать, что Удалить() проверяет, ссылаются ли на элемент документы и регистры. Не проверяет. Метод стирает запись немедленно, и в документах, где был выбран этот элемент, останутся «битые ссылки» вида <Объект не найден>. Поэтому в рабочем коде прямое удаление почти всегда идёт в паре с поиском ссылок.

Всё ниже относится к платформе 1С:Предприятие 8.3 и не зависит от конфигурации: код одинаково работает в БП 3.0, ЗУП 3.1, УТ 11 и самописных решениях.

Два базовых метода: физическое удаление и пометка

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

Метод Удалить()

Удаляет запись из таблицы справочника без возможности отката (если не идёт транзакция). Минимальный пример для формы:

&НаКлиенте
Процедура УдалитьЭлемент(Команда)
	УдалитьЭлементНаСервере(Элементы.Список.ТекущаяСтрока);
	Элементы.Список.Обновить();
КонецПроцедуры

&НаСервереБезКонтекста
Процедура УдалитьЭлементНаСервере(Ссылка)
	Если Не ЗначениеЗаполнено(Ссылка) Тогда
		Возврат;
	КонецЕсли;
	Объект = Ссылка.ПолучитьОбъект();
	Если Объект = Неопределено Тогда
		Возврат; // запись уже удалена другим пользователем
	КонецЕсли;
	Объект.Удалить();
КонецПроцедуры

Что важно знать про Удалить():

  • 🔹 вызывается только на сервере: у клиента нет доступа к объекту справочника;
  • 🔹 выполняются обработчики модуля объекта ПередУдалением — в них можно отменить удаление через Отказ = Истина;
  • 🔹 удаление группы иерархического справочника убирает и вложенные в неё элементы;
  • 🔹 записи регистров сведений, где элемент стоит в измерении со свойством Ведущее, удаляются вместе с ним; в остальных регистрах записи останутся с битой ссылкой.

Метод ПометитьНаУдаление()

Ставит флаг ПометкаУдаления и сразу записывает объект. Запись видна в списке перечёркнутой, ссылки на неё остаются рабочими.

Объект = Ссылка.ПолучитьОбъект();
Объект.ПометитьНаУдаление(Истина);  // Ложь — снять пометку

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

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

Пометку можно поставить и через свойство: Объект.ПометкаУдаления = Истина; Объект.Записать();. Результат тот же, но метод ПометитьНаУдаление() короче и не даёт забыть про запись.

Проверка ссылок перед удалением элемента справочника

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

&НаСервереБезКонтекста
Функция ЕстьСсылкиНаЭлемент(Ссылка)
	МассивСсылок = Новый Массив;
	МассивСсылок.Добавить(Ссылка);
	ТаблицаСсылок = НайтиПоСсылкам(МассивСсылок);
	Для Каждого СтрокаТЗ Из ТаблицаСсылок Цикл
		Сообщить(Строка(СтрокаТЗ.Данные) + " — " + СтрокаТЗ.Метаданные.ПолноеИмя());
	КонецЦикла;
	Возврат ТаблицаСсылок.Количество() > 0;
КонецФункции

Колонка Данные содержит объект, где найдена ссылка: документ, другой элемент справочника или ключ записи регистра. Метаданные подскажет, в каком именно объекте конфигурации искать.

Несколько практических моментов:

  • ⏱ поиск идёт по всем таблицам базы, где может храниться ссылка этого типа. На большой базе один вызов занимает секунды, поэтому передавайте массив из многих ссылок за раз, а не вызывайте функцию в цикле по одной;
  • 🔁 элементы иерархического справочника ссылаются на родителя через реквизит Родитель. Группа с детьми всегда будет «занята», пока дети существуют;
  • 🔗 подчинённый справочник ссылается на владельца через Владелец. Удалить владельца, не разобравшись с подчинёнными элементами, — верный путь к мусору в базе.

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

Замена ссылок перед удалением дубля

Самый частый повод удалять элемент кодом — дубли: два «ООО Ромашка» или три одинаковые позиции номенклатуры. Тут недостаточно найти ссылки, их нужно перевести на правильный элемент, а уже потом удалять лишний.

Писать замену вручную по всем таблицам долго и рискованно: документы придётся перепроводить, наборы записей регистров — перезаписывать. В конфигурациях на БСП для этого есть готовый программный интерфейс — функция ЗаменитьСсылки общего модуля ОбщегоНазначения. Ей передают соответствие «что заменить → на что», она обходит места использования и может сразу удалить заменённые элементы.

Для пользователя тот же механизм доступен интерактивно: обработка «Поиск и удаление дублей» в разделе Администрирование → Обслуживание. Перед тем как писать свой код, проверьте её — часто её одной хватает.

Массовое удаление записей справочника кодом

Задача «почистить справочник» обычно сводится к выборке запросом и циклу. Ниже — удаление помеченных элементов справочника Номенклатура, на которые никто не ссылается. Каждый элемент обрабатывается в своём блоке Попытка, чтобы одна ошибка не остановила весь проход.

&НаСервереБезКонтекста
Процедура УдалитьПомеченнуюНоменклатуру()
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Спр.Ссылка КАК Ссылка
	|ИЗ
	|	Справочник.Номенклатура КАК Спр
	|ГДЕ
	|	Спр.ПометкаУдаления
	|	И НЕ Спр.Предопределенный";
	МассивСсылок = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка");
	Если МассивСсылок.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;

	// Ссылки, на которые кто-то ссылается, исключаем
	Занятые = Новый Соответствие;
	Для Каждого СтрокаТЗ Из НайтиПоСсылкам(МассивСсылок) Цикл
		Занятые.Вставить(СтрокаТЗ.Ссылка, Истина);
	КонецЦикла;

	Удалено = 0;
	Для Каждого Ссылка Из МассивСсылок Цикл
		Если Занятые[Ссылка] <> Неопределено Тогда
			Продолжить;
		КонецЕсли;
		Попытка
			Объект = Ссылка.ПолучитьОбъект();
			Если Объект <> Неопределено Тогда
				Объект.Удалить();
				Удалено = Удалено + 1;
			КонецЕсли;
		Исключение
			ЗаписьЖурналаРегистрации("Очистка номенклатуры", УровеньЖурналаРегистрации.Ошибка,,
				Ссылка, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		КонецПопытки;
	КонецЦикла;
	Сообщить("Удалено элементов: " + Формат(Удалено, "ЧН=0; ЧГ=0"));
КонецПроцедуры

На что обратить внимание в этом коде:

  1. Предопределённые элементы отсекаются ещё в запросе. Код конфигурации обращается к ним по имени, и их исчезновение ломает типовые алгоритмы.
  2. НайтиПоСсылкам() вызывается один раз на весь массив.
  3. Ссылки между самими помеченными элементами тоже считаются «занятостью». Группа, все дети которой помечены, в первом проходе не удалится. Запустите процедуру повторно — после удаления детей группа освободится.
  4. Ошибки пишутся в журнал регистрации, а не теряются.

Перед массовым удалением в рабочей базе сделайте резервную копию. Физически удалённую запись нельзя вернуть ничем, кроме восстановления из копии.

Удаление без проверок: ОбменДанными.Загрузка

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

Сама платформа обработчики при этом всё равно вызывает. Но типовые конфигурации в начале ПередУдалением и ПередЗаписью проверяют этот флаг и выходят, ничего не делая. В самописном модуле объекта такой проверки может не быть — тогда флаг ничего не даст.

Пользуйтесь этим приёмом осознанно: он отключает и полезные проверки, которые защищают учёт.

Транзакции и блокировки при удалении

Одиночный Удалить() и так выполняется в неявной транзакции. Явная транзакция нужна, когда удаление связано с другими изменениями: например, сначала заменить ссылки, потом удалить элемент. Если второй шаг упадёт, первый откатится.

Шаблон по актуальному стандарту — НачатьТранзакцию() до Попытка, управляемая блокировка первым действием:

НачатьТранзакцию();
Попытка
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("Справочник.Номенклатура");
	ЭлементБлокировки.УстановитьЗначение("Ссылка", Ссылка);
	ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
	Блокировка.Заблокировать();

	Объект = Ссылка.ПолучитьОбъект();
	Если Объект <> Неопределено Тогда
		Объект.Удалить();
	КонецЕсли;

	ЗафиксироватьТранзакцию();
Исключение
	ОтменитьТранзакцию();
	ВызватьИсключение;
КонецПопытки;

Почему блокировка до ПолучитьОбъект(): пока вы не заблокировали запись, другой сеанс может её изменить или удалить. Тогда вы удалите не ту версию данных или получите исключение в середине обработки.

Для обработок с формой есть объектная блокировка Объект.Заблокировать(). Она защищает запись от одновременного редактирования в интерфейсе, но вне транзакции не заменяет управляемую блокировку.

Размер транзакции при массовой очистке

Не заворачивайте удаление тысяч элементов в одну транзакцию. Блокировки держатся до фиксации, и всё это время пользователи, работающие с этим справочником, будут ждать или получать Конфликт блокировок при выполнении транзакции. Удаляйте порциями, например по 100 элементов, фиксируя каждую порцию отдельно.

И ещё одно правило: исключение внутри транзакции нельзя «проглотить» и продолжить. После него любая запись в той же транзакции вызовет В данной транзакции уже происходили ошибки!. Поэтому Попытка на каждый элемент, как в примере выше, допустима только когда внешней транзакции нет.

Очистка помеченных объектов: интерактивно и из кода

Помеченные записи удаляют в два шага: сначала пометка, потом отдельная процедура удаления с контролем ссылок. Так устроена штатная работа в типовых конфигурациях.

Штатная обработка

В конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11) пользователь с полными правами запускает её из раздела Администрирование → Обслуживание, группа корректировки данных, команда «Удаление помеченных объектов». Обработка сама ищет ссылки и показывает, что удалить нельзя и почему.

Регламентное задание с тем же назначением включается там же — удобно для ночной очистки.

Глобальный метод УдалитьОбъекты()

Платформа даёт метод УдалитьОбъекты(МассивОбъектов, Контроль). При Контроль = Истина он удаляет только то, на что нет ссылок вне удаляемого набора. Это удобнее ручного НайтиПоСсылкам(): взаимные ссылки внутри массива (группа и её дети) не мешают удалению.

При Контроль = Ложь метод удаляет всё переданное без проверок — по сути пакетный Удалить(). Дополнительные параметры и формат возвращаемых данных о найденных ссылках смотрите в синтакс-помощнике своей версии платформы: они менялись между релизами 8.3.

Какой метод удаления выбрать

МетодКогда подходитРиски
ПометитьНаУдаление(Истина)Обычная работа: отказ от элемента, который мог использоватьсяПочти нет: обратимо, ссылки целы
Удалить() после НайтиПоСсылкам()Точечная чистка, загрузка, исправление ошибокМедленно на больших массивах
Удалить() без проверкиТестовые базы, заведомо неиспользуемые записиБитые ссылки в документах и регистрах
УдалитьОбъекты(…, Истина)Массовая очистка помеченных из кодаНужны права на удаление по всем объектам
Штатная обработка БСПРегулярная очистка силами администратораДлительная работа на большой базе

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

Исключения при удалении и как их разобрать

Диагностика: выберите симптом

Ещё одна неочевидная ловушка — подписки на событие. Обработчик ПередУдалением может жить не в модуле объекта, а в общем модуле, привязанном через Общие → Подписки на события. Если удаление отменяется без видимой причины, проверьте подписки, у которых источником указан ваш справочник.

Частые вопросы

Можно ли восстановить элемент после Удалить()?
Нет. Физическое удаление необратимо. Вернуть запись можно только из резервной копии базы, загрузив её в отдельную информационную базу и перенеся элемент.
Почему код удаления не работает на клиенте?
Объект справочника существует только на сервере. Вызывайте удаление из процедуры с директивой &НаСервере или &НаСервереБезКонтекста, передавая туда ссылку.
Попадает ли программное удаление в журнал регистрации?
Да, если для базы включено журналирование нужного уровня. Платформа фиксирует событие удаления данных с указанием пользователя и объекта.
Как удалить все элементы справочника в тестовой базе?
Выгрузите все ссылки запросом и передайте массив в УдалитьОбъекты() с Контроль = Ложь. В рабочей базе так делать нельзя.
Чем пометка через свойство отличается от метода ПометитьНаУдаление()?
По результату ничем: оба варианта ставят флаг и записывают объект. Метод просто сразу выполняет запись.