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