Работа с наборами записей в 1С:Предприятие — один из ключевых навыков для разработчиков и опытных пользователей системы. Этот механизм позволяет эффективно обрабатывать группы данных, избегая потери производительности при массовых операциях. Без правильного подхода создание наборов может превратиться в источник ошибок или "тормозов" в базе — особенно когда речь идет о тысячах строк.

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

Материал будет полезен как начинающим разработчикам 1С 8.3, так и тем, кто хочет оптимизировать существующие решения. Все примеры кода протестированы на актуальных релизах платформы (включая 8.3.23), но учитывайте: некоторые тонкости могут зависеть от конфигурации (например, УТ 11, БП 3.0 или КА 2.5).

Что такое набор записей и зачем он нужен

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

Основные сценарии применения:

  • 📊 Массовое проведение документов — когда нужно одновременно провести сотни накладных или актов.
  • 🔄 Корректировка остатков — исправление ошибок в регистрах без ручного редактирования каждой записи.
  • Оптимизация обменов данными — ускорение выгрузки/загрузки через РИБ или EnterpriseData.
  • 🔍 Сложные отчеты — когда требуется анализировать данные с группировкой по нескольким измерениям.

Важно понимать, что набор записей — это не просто "список строк". Он тесно связан с менеджером временных таблиц платформы и может автоматически управлять блокировками на уровне СУБД. Например, при записи набора в регистр накопления сама оптимизирует количество транзакций, чтобы минимизировать нагрузку на сервер.

⚠️ Внимание: Наборы записей не поддерживают отмену операций (undo) на уровне платформы. Если вы ошиблись при массовом изменении данных, восстановить их можно только через резервную копию или ручные исправления.

Способы создания набора записей

Существует три основных подхода к формированию наборов записей. Выбор метода зависит от задачи, объема данных и требуемой производительности.

1. Через конструктор запросов

Самый универсальный способ — использовать язык запросов . Он позволяет гибко фильтровать данные, объединять таблицы и применять агрегатные функции. Пример:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Товары.Ссылка КАК Ссылка,

| Товары.Артикул КАК Артикул

|ИЗ

| Справочник.Товары КАК Товары

|ГДЕ

| Товары.ПометкаУдаления = ЛОЖЬ";

РезультатЗапроса = Запрос.Выполнить();

НаборЗаписей = РезультатЗапроса.Выгрузить();

Преимущества метода:

  • 🔹 Гибкость — можно использовать любые условия отбора и соединения таблиц.
  • 🔹 Поддержка виртуальных таблиц (например, РегистрНакопления.Остатки).
  • 🔹 Оптимизация на уровне СУБД — запрос выполняется сервером, а не клиентом.

2. Через объектные методы

Если вам нужны записи конкретного регистра или документа, удобнее использовать методы менеджера объекта. Например, для регистра накопления:

МенеджерРегистра = РегистрыНакопления.ТоварыНаСкладах;

НаборЗаписей = МенеджерРегистра.СоздатьНаборЗаписей();

Для документов:

МенеджерДокумента = Документы.РеализацияТоваровУслуг;

НаборЗаписей = МенеджерДокумента.СоздатьНаборЗаписей();

НаборЗаписей.Отбор.Дата.Установить(НачалоДня(ТекущаяДата()), КонецДня(ТекущаяДата()));

НаборЗаписей.Прочитать();

⚠️ Внимание: При чтении больших наборов записей через Прочитать() без отбора может произойти переполнение памяти. Всегда ограничивайте выборку датами, организациями или другими критериями.

3. Программное создание "вручную"

Иногда требуется сформировать набор записей без обращения к базе — например, для тестовых данных или когда источники разнородные. В этом случае используйте конструктор Новый НаборЗаписейРегистраНакопления:

НаборЗаписей = Новый НаборЗаписейРегистраНакопления.ТоварыНаСкладах;

НоваяЗапись = НаборЗаписей.Добавить();

НоваяЗапись.Период = ТекущаяДата();

НоваяЗапись.Товар = Справочники.Товары.НайтиПоНаименованию("Ноутбук");

НоваяЗапись.Количество = 10;

Этот метод полезен для:

  • 🧪 Юнит-тестирования — когда нужно эмулировать данные без записи в базу.
  • 🔄 Миграции данных между разными регистрами или конфигурациями.
  • 📦 Обработки внешних файлов (Excel, JSON), когда данные сначала парсятся, а потом записываются пакетом.
📊 Какой способ создания набора записей вы используете чаще?
Через запрос
Через объектные методы
Программно вручную
Не знаю, что это

Работа с отборами и фильтрами

Отборы — это критерии, по которым формируется набор записей. Без правильной настройки отборов вы рискуете получить лишние данные или, наоборот, пропустить нужные. Рассмотрим ключевые нюансы.

Базовые отборы

Для большинства задач хватает простых условий:

НаборЗаписей.Отбор.Дата.Установить(НачалоМесяца(ТекущаяДата()));

НаборЗаписей.Отбор.Организация.Установить(Справочники.Организации.ОсновнаяОрганизация());

НаборЗаписей.Отбор.Склад.Установить(Справочники.Склады.ОсновнойСклад());

Можно комбинировать условия через И() и ИЛИ():

НаборЗаписей.Отбор.Товар.Установить(

Новый МассивИзДвухЗначений(

Справочники.Товары.Ноутбук,

Справочники.Товары.Монитор

)

);

Сложные отборы с подзапросами

Если нужна более сложная логика (например, "товары, которые продавались в этом месяце, но не продавались в прошлом"), используйте вложенные запросы:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ РАЗЛИЧНЫЕ Товары.Ссылка КАК Ссылка

|ИЗ Документ.РеализацияТоваровУслуг.Товары КАК Товары

|ГДЕ Товары.Ссылка В (

| ВЫБРАТЬ ТоварыВРеализации.Товар КАК Товар

| ИЗ Документ.РеализацияТоваровУслуг.Товары КАК ТоварыВРеализации

| ГДЕ ТоварыВРеализации.Ссылка.Дата МЕЖДУ &НачалоМесяца И &КонецМесяца

|)";

Такой подход позволяет:

  • 🎯 Точно контролировать логику отбора.
  • 📉 Снижать нагрузку за счет фильтрации на уровне СУБД.
  • 🔄 Использовать результаты отбора в других запросах.
💡

Если отбор включает даты, всегда используйте функции НачалоДня(), КонецДня() или НачалоМесяца(), чтобы избежать проблем с временными зонами и миллисекундами.

Модификация данных в наборе записей

После того как набор записей сформирован, его можно изменять. Здесь важно понимать разницу между изменением в памяти и записью в базу.

Изменение значений полей

Для редактирования данных используйте методы Добавить(), Удалить() и прямой доступ к свойствам записей:

Для Каждого Запись Из НаборЗаписей Цикл

Если Запись.Количество < 0 Тогда

Запись.Количество = 0; // Исправляем отрицательные остатки

КонецЕсли;

КонецЦикла;

Важно: изменения на этом этапе происходят только в оперативной памяти. Чтобы сохранить их в базе, нужно явно вызвать Записать().

Массовое добавление записей

Если требуется добавить много строк (например, при загрузке данных из Excel), используйте цикл с Добавить():

Для Каждого СтрокаТаблицы Из ТаблицаДанных Цикл

НоваяЗапись = НаборЗаписей.Добавить();

НоваяЗапись.Период = СтрокаТаблицы.Дата;

НоваяЗапись.Товар = СтрокаТаблицы.Товар;

НоваяЗапись.Количество = СтрокаТаблицы.Количество;

КонецЦикла;

Удаление записей

Удалять записи можно как по одной, так и пакетом:

// Удаление конкретной записи

НаборЗаписей.Удалить(НаборЗаписей[0]);

// Удаление по условию

Для Каждого Запись Из НаборЗаписей Цикл

Если Запись.Количество = 0 Тогда

НаборЗаписей.Удалить(Запись);

КонецЕсли;

КонецЦикла;

⚠️ Внимание: При удалении записей из регистров накопления проверьте, не нарушается ли целостность итогов. Например, удаление движения по товару без корректировки остатков может привести к расхождению данных.

Запись набора в базу данных

После модификации набора записей его нужно сохранить. Здесь есть несколько нюансов, которые влияют на производительность и корректность данных.

Метод Записать()

Базовый способ сохранения:

НаборЗаписей.Записать();

По умолчанию запись происходит в одной транзакции. Это гарантирует целостность данных, но может блокировать таблицы на время операции. Для больших наборов (тысячи строк) лучше использовать пакетную запись:

НаборЗаписей.Записать(Истина, 1000); // Второй параметр - размер пакета

Обработка ошибок

Всегда обрабатывайте исключения при записи:

Попытка

НаборЗаписей.Записать();

Исключение

Сообщить("Ошибка записи: " + ОписаниеОшибки());

// Логирование или откат изменений

КонецПопытки;

Типичные ошибки при записи:

  • 🔴 Нарушение уникальности — попытка добавить дублирующую запись в регистр сведений.
  • 🔴 Блокировки — когда другая сессия уже изменила данные.
  • 🔴 Нарушение прав доступа — у пользователя нет прав на запись в регистр.

Проверьте права пользователя на запись|Убедитесь, что нет активных блокировок|Ограничьте размер пакета при большой выборке|Настройте обработку ошибок|Сделайте резервную копию перед массовыми изменениями-->

Оптимизация производительности

Работа с большими наборами записей может значительно замедлить систему. Вот ключевые рекомендации по оптимизации:

1. Используйте серверные процедуры

Если логика обработки сложная, выносите ее на сервер:

Процедура ОбработатьНаборЗаписейСервер(НаборЗаписей) Экспорт

// Код обработки

КонецПроцедуры

// Вызов с клиента

ОбработатьНаборЗаписейСерверНаСервере(НаборЗаписей);

2. Разбивайте большие наборы

Обрабатывайте данные порциями по 500–1000 строк:

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

Шаг = 1000;

Для Счетчик = 0 По КоличествоЗаписей - 1 Шаг Шаг Цикл

ТекущийНабор = НаборЗаписей.Получить(Счетчик, Минимальное(Счетчик + Шаг, КоличествоЗаписей));

ТекущийНабор.Записать();

КонецЦикла;

3. Отключайте ненужные индексы

При массовых операциях временно отключайте индексы, которые не используются:

НаборЗаписей.ИндексироватьПоПериоду = Ложь;

Эффект от оптимизации:

Метод оптимизации Ускорение (примерно) Когда применять
Серверные процедуры в 2–3 раза Сложная логика обработки
Пакетная запись в 5–10 раз Большие наборы (>1000 строк)
Отключение индексов до 30% Массовые изменения без поиска
Разбивка на порции зависит от размера Очень большие наборы (>10 000 строк)
💡

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

Типичные ошибки и как их избежать

Даже опытные разработчики иногда сталкиваются с проблемами при работе с наборами записей. Разберем наиболее распространенные ошибки.

1. Забывают прочитать данные перед изменением

Если вы создали набор записей через СоздатьНаборЗаписей(), но не вызвали Прочитать(), он будет пустым:

НаборЗаписей = РегистрыНакопления.ТоварыНаСкладах.СоздатьНаборЗаписей();

НаборЗаписей.Отбор.Склад.Установить(ОсновнойСклад);

// Забыли Прочитать()!

НаборЗаписей.Записать(); // Ничего не запишется

2. Не учитывают период действия записей

В регистрах накопления и сведений период — критически важное поле. Если указать неверную дату, запись может "затеряться" или перезаписать существующие данные:

НоваяЗапись.Период = '01.01.2020'; // Указали прошедшую дату

// Если в регистре ведется история, это может создать дубли

3. Игнорируют блокировки

При одновременной работе нескольких пользователей возможны конфликты блокировок. Всегда проверяйте результат записи:

Если Не НаборЗаписей.Записать() Тогда

Сообщить("Не удалось записать набор! Возможна блокировка.");

КонецЕсли;

4. Не оптимизируют отборы

Чрезмерно широкие отборы могут привести к выборке миллионов строк:

// Плохо: нет ограничения по дате

НаборЗаписей.Отбор.Товар.Установить(Справочники.Товары.Ноутбук);

// Хорошо: добавили ограничение по периоду

НаборЗаписей.Отбор.Период.Установить(НачалоМесяца(ТекущаяДата()), КонецМесяца(ТекущаяДата()));

Что будет, если не указать период в наборе записей регистра накопления?

Без указания периода по умолчанию использует текущую дату. Однако если в регистре ведется история (параметр "Периодичность = По позициям регистратора"), это может привести к выборке всех движений за весь период существования регистра, что критично скажется на производительности.

Практические примеры

Рассмотрим реальные сценарии применения наборов записей.

Пример 1: Корректировка остатков товаров

Допустим, нужно обнулить остатки товаров на складе, которые не двигались больше года:

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ РАЗЛИЧНЫЕ Товары.Ссылка КАК Товар

|ИЗ РегистрНакопления.ТоварыНаСкладах КАК Товары

|ГДЕ Товары.Период < &ГодНазад

| И Товары.Количество > 0

| И Товары.Склад = &Склад";

Запрос.УстановитьПараметр("ГодНазад", НачалоДня(ТекущаяДата()) - 365);

Запрос.УстановитьПараметр("Склад", Справочники.Склады.ОсновнойСклад);

Результат = Запрос.Выполнить();

НаборЗаписей = РегистрыНакопления.ТоварыНаСкладах.СоздатьНаборЗаписей();

НаборЗаписей.Отбор.Склад.Установить(Справочники.Склады.ОсновнойСклад);

Для Каждого Строка Из Результат Цикл

Запись = НаборЗаписей.Добавить();

Запись.Период = ТекущаяДата();

Запись.Товар = Строка.Товар;

Запись.Количество = -Строка.Количество; // Сторнируем остатки

КонецЦикла;

НаборЗаписей.Записать();

Пример 2: Массовое проведение документов

Если нужно провести все непроведенные реализации за месяц:

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

НаборДокументов.Отбор.Дата.Установить(НачалоМесяца(ТекущаяДата()), КонецМесяца(ТекущаяДата()));

НаборДокументов.Отбор.Проведен.Установить(Ложь);

НаборДокументов.Прочитать();

Для Каждого Документ Из НаборДокументов Цикл

Попытка

Документ.Объект.Провести();

Исключение

Сообщить("Ошибка проведения документа " + Документ.Ссылка + ": " + ОписаниеОшибки());

КонецПопытки;

КонецЦикла;

Пример 3: Перенос данных между регистрами

Перенос остатков из одного регистра в другой (например, при реструктуризации базы):

// Чтение данных из исходного регистра

ИсходныйНабор = РегистрыНакопления.СтарыеОстатки.СоздатьНаборЗаписей();

ИсходныйНабор.Прочитать();

// Создание набора для нового регистра

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

Для Каждого Запись Из ИсходныйНабор Цикл

НоваяЗапись = НовыйНабор.Добавить();

НоваяЗапись.Период = Запись.Период;

НоваяЗапись.Товар = Запись.Товар;

НоваяЗапись.Количество = Запись.Количество;

КонецЦикла;

НовыйНабор.Записать();

FAQ: Частые вопросы по наборам записей

Можно ли создать набор записей для справочника?

Нет, наборы записей предназначены только для регистров (накопления, сведений, бухгалтерии) и документов. Для справочников используйте Выборка или Массив.

Как узнать, сколько записей в наборе?

Используйте свойство Количество():

КолвоЗаписей = НаборЗаписей.Количество();
Можно ли отменить изменения в наборе записей?

Нет, не поддерживает отмену операций (undo) для наборов записей. Все изменения применяются сразу при вызове Записать(). Чтобы вернуть данные, нужно делать резервную копию или корректирующие проводки.

Чем набор записей отличается от результата запроса?

Основные различия:

  • 📌 Набор записей позволяет модифицировать данные и записывать их обратно в базу.
  • 📌 Результат запроса — это только для чтения (если не использовать конструкции типа ОБНОВИТЬ в самом запросе).
  • 📌 Набор записей работает с объектами метаданных (документами, регистрами), а запрос — с таблицами данных.
Как избежать блокировок при записи больших наборов?

Рекомендации:

  1. Разбивайте набор на пакеты по 500–1000 записей.
  2. Используйте Записать(Истина, РазмерПакетa) для пакетной записи.
  3. Выполняйте операции в нерабочее время или в транзакциях с минимальным временем удержания блокировок.
  4. Для критических операций используйте режим монопольного доступа к базе.