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