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