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