Как удалить записи из независимого регистра сведений по отбору из ресурсов?

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

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

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

В чем сложность удаления по ресурсам?

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

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

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

---

Метод №1. Использование запроса и Менеджера Записи (Надежный, но медленный способ)

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

Проанализируем пример кода. Допустим, у нас есть независимый регистр сведений "ЦеныНоменклатуры" с измерениями "Номенклатура", "ТипЦен" и ресурсом "Цена". Нам нужно удалить все записи, где цена меньше 100 рублей.


// Шаг 1. Получаем ключевые поля записей, подлежащих удалению
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
    |	ЦеныНоменклатуры.ТипЦен КАК ТипЦен
    |ИЗ
    |	РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
    |ГДЕ
    |	ЦеныНоменклатуры.Цена < 100";

Выборка = Запрос.Выполнить().Выбрать();

// Шаг 2. Обходим выборку и удаляем каждую запись индивидуально
Пока Выборка.Следующий() Цикл
    МенеджерЗаписи = РегистрыСведений.ЦеныНоменклатуры.СоздатьМенеджерЗаписи();
    // Заполняем измерения для точечного позиционирования
    ЗаполнитьЗначенияСвойств(МенеджерЗаписи, Выборка);
    
    // Пытаемся прочитать и удалить запись
    МенеджерЗаписи.Прочитать();
    Если МенеджерЗаписи.Выбран() Тогда
        МенеджерЗаписи.Удалить();
    КонецЕсли;
КонецЦикла;

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

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

---

Метод №2. Оптимизированное удаление через Набор Записей (Быстрый способ)

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

Рассмотрим, как реализовать этот алгоритм правильно:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
    |	ЦеныНоменклатуры.ТипЦен КАК ТипЦен
    |ИЗ
    |	РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
    |ГДЕ
    |	ЦеныНоменклатуры.Цена < 100";

ТаблицаДляУдаления = Запрос.Выполнить().Выгрузить();

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

Счетчик = 0;
Для Каждого СтрокаИзмерения Из ТаблицаДляУдаления Цикл
    Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
    
    // Устанавливаем жесткий отбор по всем измерениям
    Набор.Отбор.Номенклатура.Установить(СтрокаИзмерения.Номенклатура);
    Набор.Отбор.ТипЦен.Установить(СтрокаИзмерения.ТипЦен);
    
    // Записываем пустой набор (без добавления строк). 
    // Метод очистит запись в БД по указанному отбору.
    Набор.Записать(Истина);
    
    Счетчик = Счетчик + 1;
    // Порционное фиксирование транзакции каждые 1000 итераций для избежания блокировок таблицы
    Если Счетчик % 1000 = 0 Тогда
        ЗафиксироватьТранзакцию();
        НачатьТранзакцию();
    КонецЕсли;
КонецДля;

// Фиксируем оставшийся хвост транзакции
Если Счетчик % 1000 <> 0 Тогда
    ЗафиксироватьТранзакцию();
КонецЕсли;

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

---

Метод №3. Метод "Инверсии" для экстремально больших регистров

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

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

Этот метод сводит операцию к двум транзакциям: полной очистке и одной массовой вставке. Однако помните о системных ограничениях: Таблица Значений со всеми сохраняемыми записями должна помещаться в оперативную память сервера приложений 1С.

Резюме и рекомендации

Выяснив особенности работы платформы, подведем итоги работы с независимыми регистрами сведений:

← На главную