Как программно удалить конкретную строку из регистра сведений, подчиненного регистратору?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с платформой «1С:Предприятие 8» разработчики часто сталкиваются с необходимостью точечной корректировки движений в регистрах. Особую сложность вызывают регистры сведений, которые подчинены регистратору. В отличие от независимых регистров, здесь нельзя просто использовать менеджер записи для удаления одной строки по ключу. Любое изменение должно происходить через объект НаборЗаписей, привязанный к конкретному документу.

В этой статье мы подробно разберем, почему стандартный метод записи не всегда приводит к удалению нужных строк, и изучим несколько надежных способов решения этой задачи.

Анализ проблемы: почему код «не удаляет»?

Рассмотрим типичную ситуацию, с которой столкнулся автор темы. Имеется периодический регистр сведений, записи в который вносит документ (регистратор). Стоит задача: оставить все записи документа, кроме одной, соответствующей определенному сотруднику и дате.

Первоначальная логика часто выглядит так: мы создаем НаборЗаписей, устанавливаем отбор по регистратору, читаем данные, находим нужную строку и... вызываем метод Записать(). Однако, как справедливо замечено в обсуждении, метод Записать() сам по себе не знает, что вы хотели что-то удалить. Если вы прочитали набор в память и записали его обратно без изменений в самой коллекции набора, состояние базы данных останется прежним. Запись набора — это полная перезапись всех движений регистратора по данному фильтру тем содержимым, которое находится в объекте в данный момент.

Решение №1: Использование промежуточной таблицы значений

Этот метод является наиболее наглядным и безопасным для начинающих разработчиков. Его суть заключается в том, чтобы выгрузить все текущие записи документа во временную таблицу, очистить набор и заполнить его заново только теми данными, которые должны остаться.

Разберем этот алгоритм по шагам:

  1. Создаем объект НаборЗаписей и устанавливаем фильтр по документу-регистратору.
  2. Читаем данные из базы в память с помощью метода Прочитать().
  3. Выгружаем содержимое набора в ТаблицаЗначений.
  4. Очищаем текущий набор записей в памяти.
  5. Обходим таблицу значений циклом и добавляем в набор только те строки, которые НЕ подлежат удалению.
  6. Записываем измененный набор в базу данных.

Рассмотрим программную реализацию этого метода:


// Создаем набор и устанавливаем отбор
НаборЗаписей = РегистрыСведений.РаботникиОрганизации.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Выборка.Регистратор);
НаборЗаписей.Прочитать();

// Выгружаем записи в ТЗ для манипуляций
тзЗаписи = НаборЗаписей.Выгрузить();

// Очищаем набор в памяти
НаборЗаписей.Очистить(); 

Для Каждого СтрокаТЗ Из тзЗаписи Цикл 
    // Проверяем условие: если это НЕ та строка, которую хотим удалить, то добавляем её обратно
    Если НЕ (СтрокаТЗ.Период = ВыборкаПоРаботникиОрганизации.ДатаДействия 
         И СтрокаТЗ.Сотрудник = ВыборкаПоРаботникиОрганизации.Сотрудник) Тогда
        
        НоваяЗапись = НаборЗаписей.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяЗапись, СтрокаТЗ);
        
    КонецЕсли;
КонецЦикла;

// Записываем итоговый результат в базу
НаборЗаписей.Записать();

Решение №2: Оптимизированное удаление напрямую из набора

Хотя метод с таблицей значений работает безотказно, он требует выделения дополнительной памяти. Мы можем оптимизировать процесс, удаляя строки непосредственно из коллекции НаборЗаписей. Здесь есть важный технический нюанс: при удалении элементов из коллекции в цикле «сверху вниз» индексы оставшихся элементов смещаются, что приводит к ошибкам или пропуску строк.

Правильный подход — обходить коллекцию с конца (с последнего индекса к нулевому). Рассмотрим, как это выглядит на практике:


НаборЗаписей = РегистрыСведений.ИмяРегистра.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
НаборЗаписей.Прочитать();

КоличествоЗаписей = НаборЗаписей.Количество();

// Обходим с конца к началу
Для Индекс = 1 По КоличествоЗаписей Цикл
    ТекущийИндекс = КоличествоЗаписей - Индекс;
    Запись = НаборЗаписей[ТекущийИндекс];
    
    // Проверяем условие удаления
    Если Запись.Сотрудник = НужныйСотрудник Тогда
        НаборЗаписей.Удалить(ТекущийИндекс);
    КонецЕсли;
КонецЦикла;

// Если набор изменился, записываем его
НаборЗаписей.Записать();

Использование метода Удалить() по индексу внутри обратного цикла — это наиболее производительный способ модификации больших наборов данных.

Важные замечания и риски

Проанализируем ситуацию глубже. Нужно понимать, что регистры, подчиненные регистратору, живут «одной жизнью» с документом. Это накладывает определенные ограничения на ручное управление записями:

  1. Проблема перепроведения: Если вы программно удалили строку из регистра, но затем пользователь откроет документ-регистратор и нажмет «Провести», платформа вызовет стандартный обработчик проведения. Если в коде документа заложено формирование этой «удаленной» строки, она появится снова. Для стабильного результата нужно вносить правки в сам алгоритм проведения документа (модуль объекта, процедура ОбработкаПроведения).
  2. Транзакционные блокировки: При вызове метода Записать() на регистр накладывается исключительная блокировка по данному регистратору. Старайтесь выполнять такие операции максимально быстро, чтобы не блокировать работу других пользователей.
  3. Замещение записей: По умолчанию метод Записать() вызывается с параметром Замещать = Истина. Это означает, что старые записи по данному регистратору полностью удаляются из базы и заменяются новыми данными из вашего объекта НаборЗаписей. Если вы случайно запишете пустой набор (забыв вызвать Прочитать() или некорректно заполнив его), вы удалите все движения документа по этому регистру.

Рекомендация по архитектуре

Если ваша бизнес-логика требует частого удаления отдельных строк в обход проведения документов, возможно, архитектура регистра выбрана неверно. Для таких случаев лучше использовать Независимый регистр сведений. В нем каждая запись уникальна по набору измерений и не зависит от наличия или состояния какого-либо документа. Удаление в независимом регистре выполняется гораздо проще — через МенеджерЗаписи с установкой ключевых полей.

Однако, если вы работаете в рамках типовой конфигурации, где структура уже задана, придерживайтесь метода с обратным обходом набора записей, предварительно убедившись, что данные не будут перезаписаны при регламентном перепроведении документов.

← На главную