При работе с механизмами платформы 1С:Предприятие разработчики часто сталкиваются с необходимостью очистки или выборочного удаления данных из регистров сведений. Если регистр независимый и непериодический, стандартным решением является использование объекта РегистрСведенийНаборЗаписей. Однако здесь кроется серьезное ограничение платформы: отбор в наборе записей можно установить только по измерениям регистра. Установить отбор напрямую по ресурсам или реквизитам невозможно.
В этой статье мы подробно разберем, как обойти это ограничение, проанализируем различные подходы, сопоставим их по скорости работы и безопасности, а также приведем готовые примеры кода для реализации оптимального удаления данных.
В СУБД структура таблиц регистров сведений оптимизирована таким образом, что кластерный индекс строится по измерениям (а также по периоду для периодических регистров). Платформа 1С жестко контролирует запись в эти таблицы. Когда мы инициализируем НаборЗаписей и накладываем отбор, 1С требует указания значений ключевых полей (измерений). Если попытаться записать набор без указания измерений, платформа просто сотрет абсолютно все данные из регистра, так как пустой набор записей с пустым отбором перезаписывает (очищает) всю таблицу.
Для удаления записей, где условие фильтрации наложено на ресурсы, нам придется применить комбинированный подход: сначала выбрать записи программным путем (через запрос), а затем точечно удалить их из базы данных.
Рассмотрим по шагам основные и наиболее эффективные методы решения этой задачи.
---
Этот способ прекрасно подходит для небольшого объема данных (до нескольких тысяч записей). Суть метода заключается в том, что мы запросом выбираем ключевые поля (измерения) тех записей, ресурсы которых соответствуют нашему условию. Затем в цикле обходим результат запроса, позиционируем МенеджерЗаписи на каждой строке и вызываем метод Удалить().
Проанализируем пример кода. Допустим, у нас есть независимый регистр сведений "ЦеныНоменклатуры" с измерениями "Номенклатура", "ТипЦен" и ресурсом "Цена". Нам нужно удалить все записи, где цена меньше 100 рублей.
// Шаг 1. Получаем ключевые поля записей, подлежащих удалению
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
| ЦеныНоменклатуры.ТипЦен КАК ТипЦен
|ИЗ
| РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
|ГДЕ
| ЦеныНоменклатуры.Цена < 100";
Выборка = Запрос.Выполнить().Выбрать();
// Шаг 2. Обходим выборку и удаляем каждую запись индивидуально
Пока Выборка.Следующий() Цикл
МенеджерЗаписи = РегистрыСведений.ЦеныНоменклатуры.СоздатьМенеджерЗаписи();
// Заполняем измерения для точечного позиционирования
ЗаполнитьЗначенияСвойств(МенеджерЗаписи, Выборка);
// Пытаемся прочитать и удалить запись
МенеджерЗаписи.Прочитать();
Если МенеджерЗаписи.Выбран() Тогда
МенеджерЗаписи.Удалить();
КонецЕсли;
КонецЦикла;
Преимущества: Абсолютная безопасность. Вы никогда не удалите лишние записи, так как менеджер записи четко позиционируется по полному уникальному ключу (комбинации всех измерений).
Недостатки: Низкая скорость работы на больших массивах данных. Метод МенеджерЗаписи.Удалить() генерирует отдельную транзакцию в СУБД на каждую строчку, что сильно нагружает дисковую подсистему сервера.
---
Чтобы ускорить процесс удаления, мы можем использовать свойство набора записей: если записать пустой набор с установленным отбором по измерениям, то все записи, соответствующие этому отбору, будут удалены за одну операцию СУБД. Это работает значительно быстрее, чем использование менеджера записи.
Рассмотрим, как реализовать этот алгоритм правильно:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныНоменклатуры.Номенклатура КАК Номенклатура,
| ЦеныНоменклатуры.ТипЦен КАК ТипЦен
|ИЗ
| РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
|ГДЕ
| ЦеныНоменклатуры.Цена < 100";
ТаблицаДляУдаления = Запрос.Выполнить().Выгрузить();
// Начинаем транзакцию для ускорения записи на диск
НачатьТранзакцию();
Счетчик = 0;
Для Каждого СтрокаИзмерения Из ТаблицаДляУдаления Цикл
Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
// Устанавливаем жесткий отбор по всем измерениям
Набор.Отбор.Номенклатура.Установить(СтрокаИзмерения.Номенклатура);
Набор.Отбор.ТипЦен.Установить(СтрокаИзмерения.ТипЦен);
// Записываем пустой набор (без добавления строк).
// Метод очистит запись в БД по указанному отбору.
Набор.Записать(Истина);
Счетчик = Счетчик + 1;
// Порционное фиксирование транзакции каждые 1000 итераций для избежания блокировок таблицы
Если Счетчик % 1000 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецДля;
// Фиксируем оставшийся хвост транзакции
Если Счетчик % 1000 <> 0 Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Важное предупреждение по безопасности: При использовании этого метода необходимо следить за тем, чтобы все измерения ключа были заполнены в отборе. Если одно из измерений окажется незаполненным (например, равно значению Неопределено или пустой ссылке из-за логической ошибки), платформа удалит все записи, у которых совпали остальные измерения, что может привести к потере нужных данных. Будьте предельно внимательны при установке отборов!
---
Если вам необходимо очистить регистр, в котором содержатся миллионы строк, построчный обход (даже через наборы записей) займет слишком много времени и заблокирует работу пользователей. В таких случаях мы рекомендуем использовать метод инверсии данных:
Набор.Записать(Истина). Это мгновенно и полностью очистит весь регистр сведений на уровне СУБД.Этот метод сводит операцию к двум транзакциям: полной очистке и одной массовой вставке. Однако помните о системных ограничениях: Таблица Значений со всеми сохраняемыми записями должна помещаться в оперативную память сервера приложений 1С.
Выяснив особенности работы платформы, подведем итоги работы с независимыми регистрами сведений: