Приветствуем вас! Каждый разработчик на платформе «1С:Предприятие 8» регулярно сталкивается с необходимостью программной записи или удаления данных в регистрах сведений. Работа с объектом НаборЗаписей — это стандартный и наиболее эффективный инструмент для массовой модификации данных. Однако на практике нередко возникают ситуации, когда отбор, установленный программно, «не отрабатывает» или возвращает пустой результат, из-за чего данные в базе не обновляются или не удаляются.
В этой статье мы подробно разберем, почему возникает ситуация, когда программа «не хочет понимать» установленный отбор, детально проанализируем ошибки типизации на примере реального кейса с технического форума, а также изучим лучшие современные практики работы с отборами в наборах записей регистров сведений, включая новые оптимизированные возможности платформы версий 8.3.25, 8.3.26 и 8.3.27.
Представим типичную рабочую ситуацию. Нам необходимо выполнить отбор в независимом регистре сведений по двум измерениям: Изделие и РесурсыИзделия. Мы пишем стандартный код установки отборов:
НаборЗаписей = РегистрыСведений.Ресурсы.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.РесурсыИзделия.Установить(СтруктураЗаписи.РесурсыИзделия);
НаборЗаписей.Отбор.Изделие.Установить(СтруктураЗаписи.Изделие);
НаборЗаписей.Прочитать();
Проверяем выполнение кода в отладчике и обнаруживаем странное поведение: отбор по измерению Изделие успешно применяется, а вот по измерению РесурсыИзделия отбор возвращает пустой результат (записи не найдены), либо вовсе игнорируется платформой.
Выясним первопричину такого поведения. Как показывает практика, в 90% случаев корень проблемы кроется в несоответствии типов данных. При заполнении структуры СтруктураЗаписи разработчик может ошибочно передать ссылку на другой справочник. Например, измерение регистра РесурсыИзделия имеет тип СправочникСсылка.Ресурсы, а в структуру данных из внешней процедуры по ошибке записывается ссылка из справочника СправочникСсылка.Номенклатура.
Даже если визуально наименования элементов в разных справочниках совпадают (например, «Винт М4» присутствует в обоих списках), для СУБД и платформы «1С» это абсолютно разные типы данных с уникальными идентификаторами (UUID). В результате, при попытке наложить отбор по значению несовпадающего типа, система возвращает пустой набор записей.
Давайте разберем по шагам, как проверить типы данных с помощью отладчика:
ТипЗнч(НаборЗаписей.Отбор.РесурсыИзделия.Значение) или изучите описание метаданных регистра в Конфигураторе.ТипЗнч(СтруктураЗаписи.РесурсыИзделия).СправочникСсылка.Ресурсы и СправочникСсылка.Компоненты), вам необходимо локализовать и исправить код, отвечающий за формирование исходной структуры данных.Чтобы не совершать досадных ошибок при работе с регистрами сведений, давайте детально проанализируем архитектурные ограничения, накладываемые платформой «1С» на объект Отбор в наборах записей:
Отбор в наборе записей предназначен исключительно для точечной фильтрации. Вы не можете использовать виды сравнения вроде «Больше», «В списке» или «Не равно». Платформа поддерживает строго операцию «Равно» (=). Если вам требуются сложные условия отбора, решать задачу придется через предварительный запрос (Запрос) с последующим обходом выборки.Рассмотрим задачу, которую часто приходится решать на практике: быстрое удаление записей из независимого регистра сведений по определенным значениям измерений.
Многие начинающие программисты совершают классическую ошибку: сначала считывают весь набор записей в память с помощью метода Прочитать(), затем очищают коллекцию записей методом Очистить(), и только после этого записывают пустой набор в СУБД. Это крайне неоптимальный путь, создающий избыточную нагрузку на базу данных и сетевой трафик.
Давайте научимся делать это правильно и максимально быстро. Для удаления записей по отбору достаточно записать пустой набор без предварительного чтения:
// Инициализируем пустой набор записей без обращения к базе данных для чтения
НаборДляУдаления = РегистрыСведений.Ресурсы.СоздатьНаборЗаписей();
// Устанавливаем точные отборы по измерениям
НаборДляУдаления.Отбор.Изделие.Установить(НужноеИзделие);
НаборДляУдаления.Отбор.РесурсыИзделия.Установить(НужныйРесурс);
// Записываем пустой набор с параметром Замещать = Истина
НаборДляУдаления.Записать(Истина);
Проанализируем, почему этот метод работает мгновенно. При вызове метода Записать(Истина) платформа «1С» посылает СУБД прямую команду на удаление (операция DELETE) всех строк в таблице регистра, которые соответствуют заданному отбору. Поскольку мы не вызывали метод Прочитать(), данные не передавались из СУБД на сервер приложений «1С», что существенно экономит ресурсы.
Технологическая платформа «1С:Предприятие» активно развивается. Начиная с версий 8.3.25 и 8.3.26, в платформу были внедрены революционные изменения, кардинально меняющие подход к записи и обновлению регистров сведений. Теперь вместо привычного булева параметра Замещение в метод Записать() можно передавать значения системного перечисления РежимЗамещения.
Давайте подробно разберем новые режимы и сценарии их использования:
РежимЗамещения.Удаление (появился в 8.3.25/8.3.26) — этот режим позволяет производить точечное удаление. Платформа анализирует записи внутри самого набора в памяти и удаляет из СУБД только те строки, ключевые поля (измерения) которых полностью совпадают с записями в наборе. Это избавляет от необходимости вручную настраивать сложные отборы.РежимЗамещения.Слияние (появился в 8.3.25) — великолепное решение для пакетного обновления. Данный режим выполняет так называемую операцию UPSERT в терминах СУБД: если запись с такими ключевыми полями уже существует, она обновляется (перезаписываются ресурсы и реквизиты), а если такой записи еще нет, она создается.РежимЗамещения.Обновление (появился в платформе 8.3.27) — этот режим ориентирован на обновление исключительно существующих в базе данных записей. Если ключевые поля совпадают, ресурсы обновляются. Новые записи при этом не добавляются в СУБД. Это колоссально повышает производительность при пакетной обработке больших массивов информации.Важнейшая рекомендация от фирмы «1С»: При использовании новых режимов замещения Слияние и Удаление разработчикам настоятельно не рекомендуется устанавливать свойство Отбор. В этих сценариях состав модифицируемых данных определяется исключительно коллекцией записей внутри самого набора. Ручная установка отбора при использовании данных режимов может привести к непредсказуемым ошибкам во время транзакции.
Еще один подводный камень, о который часто спотыкаются разработчики — это работа с измерениями, имеющими составной тип данных. Давайте разберем по шагам, как правильно работать с такими измерениями в отборах.
Если измерение регистра сведений может принимать значения нескольких типов (например, это может быть ссылка на справочник Контрагенты или справочник ФизическиеЛица), при установке отбора крайне важно явно указывать тип передаваемого значения.
Если вы попытаетесь передать в отбор пустое значение неопределенного типа (например, просто Неопределено или пустое значение не того типа), отбор отработает некорректно. Рассмотрим пример правильного заполнения отбора для составного типа:
НаборЗаписей = РегистрыСведений.УчастникиПроектов.СоздатьНаборЗаписей();
// Предположим, измерение Участник имеет составной тип.
// Мы хотим отобрать записи, где участник не заполнен, но именно как пустая ссылка конкретного справочника:
ПустаяСсылкаКонтрагента = Справочники.Контрагенты.PushayaСсылка();
НаборЗаписей.Отбор.Участник.Установить(ПустаяСсылкаКонтрагента);
НаборЗаписей.Прочитать();
Помните, что любое обращение к свойствам элемента отбора или вызов метода Установить() переводит этот элемент в состояние «Инициализирован» (свойство Использование = Истина). Если вы случайно инициализировали элемент отбора пустым значением или забыли отключить использование фильтра, платформа выполнит запрос к СУБД с поиском пустых значений, что приведет к получению пустого результата вместо всех записей.
Если вы повторно используете один и тот же объект НаборЗаписей в цикле (что часто делается для оптимизации выделения памяти), обязательно сбрасывайте состояние отборов на каждой итерации. Для этого у коллекции отборов есть специальный метод:
НаборЗаписей.Отбор.Сбросить();
Проанализируем вопросы производительности и безопасности при многопользовательской работе. Когда вы считываете набор записей, вносите изменения и записываете его обратно внутри транзакции, платформа автоматически накладывает блокировки на уровне СУБД.
Однако при высокой интенсивности параллельной работы пользователей (например, при проведении документов в крупных ERP-системах) автоматических блокировок может быть недостаточно. Чтобы предотвратить возникновение критических взаимоблокировок (Deadlocks), мы рекомендуем использовать объект БлокировкаДанных перед выполнением записи набора.
Давайте посмотрим на пример правильного наложения блокировки:
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.Ресурсы");
// Устанавливаем пространство блокировки, полностью соответствующее нашему будущему отбору
ЭлементБлокировки.УстановитьЗначение("Изделие", СтруктураЗаписи.Изделие);
ЭлементБлокировки.УстановитьЗначение("РесурсыИзделия", СтруктураЗаписи.РесурсыИзделия);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
НачатьТранзакцию();
Попытка
Блокировка.Заблокировать();
НаборЗаписей = РегистрыСведений.Ресурсы.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Изделие.Установить(СтруктураЗаписи.Изделие);
НаборЗаписей.Отбор.РесурсыИзделия.Установить(СтруктураЗаписи.РесурсыИзделия);
// Выполняем запись пустого набора для удаления записей
НаборЗаписей.Записать(Истина);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Этот паттерн проектирования гарантирует стабильную работу вашей системы в условиях высокой параллельной нагрузки и исключает сбои, вызванные коллизиями доступа к таблицам СУБД.
Разберем, в каких ситуациях вместо объекта НаборЗаписей эффективнее и проще использовать МенеджерЗаписи.
Если вам необходимо прочитать, изменить или удалить строго одну запись по известным значениям всех ключевых полей (измерений), то использование набора записей может быть избыточным. Для этих целей в платформе «1С» предназначен объект МенеджерЗаписи.
Рассмотрим пример удаления одной конкретной записи через менеджер:
Менеджер = РегистрыСведений.Ресурсы.СоздатьМенеджерЗаписи();
Менеджер.Изделие = НужноеИзделие;
Менеджер.РесурсыИзделия = НужныйРесурс;
// Пытаемся прочитать конкретную запись
Менеджер.Прочитать();
Если Менеджер.Выбран() Тогда
// Если запись существует, мы можем удалить ее напрямую
Менеджер.Удалить();
КонецЕсли;
Этот код выглядит лаконично. Однако помните ключевое различие: МенеджерЗаписи оперирует исключительно одной строкой таблицы и всегда требует установки значений абсолютно всех измерений регистра сведений. Если вы не укажете хотя бы одно измерение, СУБД запишет или удалит строку с пустым значением этого измерения. НаборЗаписей, напротив, позволяет работать с группами строк, устанавливая отбор лишь по части измерений (или вообще без отборов, перезаписывая весь регистр).
Надеемся, что этот детальный разбор механизмов фильтрации и работы с наборами записей поможет вам писать чистый, производительный и отказоустойчивый код в ваших прикладных решениях на платформе «1С:Предприятие 8»!