В процессе работы с конфигурациями 1С часто возникает ситуация, когда к одному и тому же объекту (владельцу) прикрепляется несколько файлов с одинаковым наименованием. Например, это может быть файл "логотип.png" или "Договор.pdf", который загружался несколько раз в разные периоды времени. В базе данных накапливается избыточная информация, и перед разработчиком встает задача: оставить только одну, самую актуальную версию файла по дате создания, а все остальные пометить на удаление или удалить физически.
В этой статье мы подробно разберем, как реализовать механизм очистки дублей с помощью запроса и встроенного языка 1С, проанализируем ошибки новичков и рассмотрим способы оптимизации процесса для больших баз данных.
Самый наглядный и простой в реализации способ заключается в использовании итогов в запросе. Мы сгруппируем данные по владельцу и наименованию файла, а внутри каждой группы отсортируем файлы по дате создания в обратном порядке (от новых к старым). Таким образом, самый свежий файл всегда будет первым в выборке.
Рассмотрим по шагам реализацию этого алгоритма. Сначала подготовим запрос, который выберет все необходимые данные:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПрисоединенныеФайлы.Ссылка КАК Ссылка,
| ПрисоединенныеФайлы.ВладелецФайла КАК ВладелецФайла,
| ПрисоединенныеФайлы.Наименование КАК Наименование,
| ПрисоединенныеФайлы.ДатаСоздания КАК ДатаСоздания
|ИЗ
| Справочник.БИТ_СТ_ГруппыНоменклатурыВКаталогеПрисоединенныеФайлы КАК ПрисоединенныеФайлы
|ГДЕ
| ПрисоединенныеФайлы.ПометкаУдаления = ЛОЖЬ
|
|УПОРЯДОЧИТЬ ПО
| ВладелецФайла,
| Наименование,
| ДатаСоздания УБЫВ
|ИТОГИ ПО
| ВладелецФайла,
| Наименование";
Разберем, что происходит в этом запросе. Мы выбираем Ссылка, чтобы иметь возможность изменять объект, и поля группировки. Ключевой момент здесь — конструкция УПОРЯДОЧИТЬ ПО ... ДатаСоздания УБЫВ. Она гарантирует, что при обходе детальных записей первой строкой будет идти файл с максимальной (самой поздней) датой создания.
Теперь реализуем программный обход этой выборки:
РезультатЗапроса = Запрос.Выполнить();
// Используем обход по группировкам
ВыборкаВладелец = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаВладелец.Следующий() Цикл
ВыборкаНаименование = ВыборкаВладелец.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаНаименование.Следующий() Цикл
ПервыйФайл = Истина;
ВыборкаДетали = ВыборкаНаименование.Выбрать();
Пока ВыборкаДетали.Следующий() Цикл
// Самый первый файл в группе (самый свежий) мы пропускаем
Если ПервыйФайл Тогда
ПервыйФайл = Ложь;
Продолжить;
КонецЕсли;
// Все остальные файлы — это дубликаты, которые нужно пометить на удаление
ОбъектФайла = ВыборкаДетали.Ссылка.ПолучитьОбъект();
Если ОбъектФайла <> Неопределено Тогда
ОбъектФайла.УстановитьПометкуУдаления(Истина);
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
Важное замечание: В коде выше обязательно используется оператор Продолжить после того, как мы определили первый (самый новый) файл. Без этого оператора алгоритм пометит на удаление абсолютно все файлы, включая актуальный.
Если в вашем справочнике десятки или сотни тысяч записей, использование ПолучитьОбъект() внутри цикла может существенно замедлить работу. Проанализируем ситуацию: нам не нужно перебирать все файлы, нам нужны только те, которые НЕ являются самыми новыми. Мы можем вычислить список этих ссылок прямо в запросе.
Рассмотрим более сложный, но эффективный запрос:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Файлы.ВладелецФайла КАК ВладелецФайла,
| Файлы.Наименование КАК Наименование,
| МАКСИМУМ(Файлы.ДатаСоздания) КАК МаксДата
|ПОМЕСТИТЬ ВТ_МаксимальныеДаты
|ИЗ
| Справочник.БИТ_СТ_ГруппыНоменклатурыВКаталогеПрисоединенныеФайлы КАК Файлы
|ГДЕ
| Файлы.ПометкаУдаления = ЛОЖЬ
|СГРУППИРОВАТЬ ПО
| Файлы.ВладелецФайла,
| Файлы.Наименование
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Файлы.Ссылка КАК Ссылка
|ИЗ
| Справочник.БИТ_СТ_ГруппыНоменклатурыВКаталогеПрисоединенныеФайлы КАК Файлы
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_МаксимальныеДаты КАК МаксДаты
| ПО Файлы.ВладелецФайла = МаксДаты.ВладелецФайла
| И Файлы.Наименование = МаксДаты.Наименование
|ГДЕ
| Файлы.ДатаСоздания < МаксДаты.МаксДата
| И Файлы.ПометкаУдаления = ЛОЖЬ";
Результат = Запрос.Выполнить().Выгрузить();
МассивСсылок = Результат.ВыгрузитьКолонку("Ссылка");
Преимущество этого метода в том, что мы получаем плоский список ссылок "на удаление" без сложной вложенности циклов Пока ... Следующий.
Проанализируем важный технический нюанс: поле ДатаСоздания имеет точность до секунды. Если в вашу систему файлы загружались программно (например, при импорте из другой системы), может возникнуть ситуация, когда у двух разных файлов одного владельца абсолютно одинаковая дата и время создания.
В этом случае условие ДатаСоздания < МаксДата может не сработать или оставить несколько "актуальных" файлов. Чтобы этого избежать, рекомендуется использовать поле МоментВремени или дополнительно сравнивать файлы по внутренней ссылке (Ссылка). Даже если время совпадает, ссылка у файлов всегда уникальна, и вы можете оставить тот файл, чья ссылка "больше" (была создана позже в терминах СУБД).
Перед тем как запускать подобные обработки на рабочих данных (продуктиве), обязательно выполните следующие действия:
Объект.Удалить() удаляет запись из базы безвозвратно. Метод Объект.УстановитьПометкуУдаления(Истина) позволяет проверить результат работы и, в случае ошибки, легко откатиться назад.РаботаСФайлами.НайтиСсылки().Рассмотрим пример того, как ускорить запись изменений при большом количестве файлов. Чтобы система не тратила время на проверку прав и выполнение лишних подписок на события при каждом изменении, используйте свойство ОбменДанными.Загрузка:
Для Каждого СсылкаНаФайл Из МассивСсылок Цикл
ОбъектФайла = СсылкаНаФайл.ПолучитьОбъект();
Если ОбъектФайла <> Неопределено Тогда
ОбъектФайла.ОбменДанными.Загрузка = Истина;
ОбъектФайла.УстановитьПометкуУдаления(Истина);
// Если база очень большая, можно фиксировать транзакцию порциями
КонецЕсли;
КонецЦикла;
Выясним причину, почему стоит избегать удаления "по наименованию" без учета расширения. Часто пользователи сохраняют файлы с именами вроде "Скан.pdf" и "Скан.jpg". С точки зрения системы это разные типы данных, но запрос по наименованию может посчитать их дублями. Рекомендуем всегда включать в условия группировки поле расширения файла, если оно хранится отдельно.
Подводя итог, мы рассмотрели два эффективных способа очистки базы от старых версий присоединенных файлов. Выбор метода зависит от объема вашей базы данных и необходимости в тонкой настройке логики удаления. Сочетание правильного запроса с итогами и мер предосторожности позволит вам поддерживать порядок в системе и экономить дисковое пространство.