Как ограничить набор записей регистра: Отбор.Установить, запросы и СКД
Содержание
Отбор в наборе записей регистра устанавливают методом Установить() у элемента отбора: Набор.Отбор.Регистратор.Установить(Документ) или Набор.Отбор.Номенклатура.Установить(Товар). После этого Прочитать() загрузит только подходящие записи, а Записать() заменит в базе ровно их. Это механизм платформы 1С:Предприятие 8.3, от конфигурации он не зависит.
Набор записей понимает только условие «равно» и только по полям, которые платформа разрешает для этого вида регистра. Всё сложнее — интервалы, списки, «не равно» — делается запросом или отбором системы компоновки данных. Ниже разобраны все три способа и случаи, где каждый уместен.
Как устроен отбор набора записей и чем он отличается от условия в запросе
У объекта РегистрСведенийНаборЗаписей, РегистрНакопленияНаборЗаписей и других наборов есть свойство Отбор. Это коллекция элементов, по одному на каждое разрешённое поле. Элемент хранит три вещи:
- 🔹
Значение— с чем сравнивать; - 🔹
Использование— включено ли условие; - 🔹
ВидСравнения— всегда «равно», изменить его нельзя.
Отбор набора работает в обе стороны. При чтении он ограничивает выборку. При записи он определяет, какие записи в базе будут удалены и заменены содержимым набора. Это главное отличие от запроса, который только читает.
Какие поля доступны в отборе
| Вид регистра | Поля отбора набора |
|---|---|
| Регистр сведений, подчинённый регистратору | Регистратор |
| Регистр сведений, независимый, периодический | Период и все измерения |
| Регистр сведений, независимый, непериодический | Все измерения |
| Регистр накопления | Регистратор |
| Регистр бухгалтерии | Регистратор |
| Регистр расчёта | Регистратор |
Отсюда частое разочарование: у регистра накопления нельзя написать Набор.Отбор.Склад.Установить(...). Такого элемента в коллекции нет, будет ошибка «Поле объекта не обнаружено». Выбрать остатки по складу можно только запросом.
Когда что использовать
| Задача | Инструмент |
|---|---|
| Перезаписать движения документа | Отбор набора по Регистратор |
| Изменить записи независимого регистра сведений по измерениям | Отбор набора по измерениям и Период |
| Прочитать данные с условиями «больше», «в списке», «в иерархии» | Запрос с ГДЕ или параметрами виртуальной таблицы |
| Дать пользователю фильтр в отчёте или списке | Отбор СКД |
Установка отбора в наборе записей программно
Порядок всегда одинаковый: создать набор, установить отбор, прочитать или заполнить, записать.
Независимый регистр сведений
В примере — периодический регистр сведений
ЦеныТоваров с измерениями Товар и ВидЦены и ресурсом Цена. Задача: установить новую цену на дату.
Набор = РегистрыСведений.ЦеныТоваров.СоздатьНаборЗаписей();
Набор.Отбор.Период.Установить(ДатаЦены);
Набор.Отбор.Товар.Установить(Товар);
Набор.Отбор.ВидЦены.Установить(ВидЦены);
Запись = Набор.Добавить();
Запись.Период = ДатаЦены;
Запись.Товар = Товар;
Запись.ВидЦены = ВидЦены;
Запись.Цена = НоваяЦена;
Набор.Записать(); // заменит только запись с этими датой, товаром и видом цены
Значения полей новой записи должны совпадать с отбором. Если в записи окажется другой товар, платформа откажет в записи: запись не соответствует отбору набора.
Чтение и изменение существующих записей
Набор = РегистрыСведений.ЦеныТоваров.СоздатьНаборЗаписей();
Набор.Отбор.Товар.Установить(Товар);
Набор.Отбор.ВидЦены.Установить(ВидЦены);
Набор.Прочитать();
Для Каждого Запись Из Набор Цикл
Запись.Цена = Окр(Запись.Цена * 1.1, 2);
КонецЦикла;
Набор.Записать();
Здесь Период не задан, поэтому набор прочитает всю историю цен товара по виду цены. Если цена 1000 руб. на 01.03 и 1200 руб. на 01.06, после записи будет 1100 и 1320 руб.
Движения документа
Для регистров с регистратором набор фильтруют по ссылке на документ:
Набор = РегистрыНакопления.ОстаткиТоваров.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(ДокументСсылка);
Набор.Прочитать();
// корректировка записей
Набор.Записать();
В модуле проведения писать это вручную не нужно. Наборы в коллекции Движения уже отобраны по текущему документу: достаточно заполнить Движения.ОстаткиТоваров и установить Движения.ОстаткиТоваров.Записывать = Истина.
Полезные методы коллекции Отбор
- 🔸
Набор.Отбор.Товар.Установить(Значение, Ложь)— задать значение, но не включать условие; - 🔸
Набор.Отбор.Сбросить()— снять все условия; - 🔸
Набор.Отбор.Найти("Товар")— получить элемент по имени, если имя поля приходит строкой; - 🔸
Набор.Отбор.Товар.Использование = Ложь— отключить одно условие, не трогая значение.
Вариант с Найти нужен в универсальном коде, который обходит измерения из метаданных:
Для Каждого Измерение Из Метаданные.РегистрыСведений.ЦеныТоваров.Измерения Цикл
ЭлементОтбора = Набор.Отбор.Найти(Измерение.Имя);
Если ЭлементОтбора <> Неопределено Тогда
ЭлементОтбора.Установить(ИсходнаяЗапись[Измерение.Имя]);
КонецЕсли;
КонецЦикла;
Несколько наборов в одной транзакции
Если меняете цены сразу по нескольким видам цен, пишите все наборы в одной транзакции. Тогда при ошибке на третьем наборе первые два откатятся, и в регистре не останется половины изменений.
НачатьТранзакцию();
Попытка
Для Каждого ВидЦены Из СписокВидовЦен Цикл
Набор = РегистрыСведений.ЦеныТоваров.СоздатьНаборЗаписей();
Набор.Отбор.Период.Установить(ДатаЦены);
Набор.Отбор.Товар.Установить(Товар);
Набор.Отбор.ВидЦены.Установить(ВидЦены);
Запись = Набор.Добавить();
Запись.Период = ДатаЦены;
Запись.Товар = Товар;
Запись.ВидЦены = ВидЦены;
Запись.Цена = НоваяЦена;
Набор.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Набор создаётся заново на каждой итерации. Если переиспользовать один объект и только менять значение отбора, не забудьте Набор.Очистить(): иначе в наборе останутся записи прошлого вида цены, и запись упадёт с ошибкой несоответствия отбору.
Записать() и Записать(Ложь): что произойдёт с данными
Метод Записать() принимает параметр Замещение, по умолчанию Истина.
| Вызов | Что делает в базе |
|---|---|
Набор.Записать() | Удаляет все записи, подходящие под отбор, и пишет содержимое набора |
Набор.Записать(Ложь) | Добавляет записи набора к существующим, ничего не удаляя |
Набор.Записать() при пустом наборе | Просто удаляет записи по отбору |
Последний вариант — штатный способ удалить записи регистра сведений по измерению или движения документа:
Набор = РегистрыСведений.ЦеныТоваров.СоздатьНаборЗаписей();
Набор.Отбор.Товар.Установить(Товар);
Набор.Записать(); // вся история цен товара удалена
Набор без отбора при Записать() очищает весь регистр. Если опечатка в условии Если пропустила строку с Установить, после записи в регистре останутся только записи из набора. Проверяйте такой код на копии базы.
С Записать(Ложь) у независимого регистра сведений легко получить ошибку дублирования. Если запись с теми же периодом и измерениями уже есть, платформа сообщит, что запись с такими ключевыми полями существует.
Условия отбора в конструкторе запросов
Когда набору записей не хватает возможностей, данные выбирают запросом. Конструктор запросов открывается в конфигураторе из контекстного меню модуля: Конструктор запроса....
Вкладка «Условия»
- На вкладке «Таблицы и поля» выберите таблицу регистра.
- Перейдите на вкладку «Условия».
- Перетащите поле, например
Склад, в нижнюю часть окна. - Конструктор подставит условие
Склад = &Склади создаст параметр. - Для «больше», «В», «ПОДОБНО» отметьте флажок «Произвольное» и напишите выражение вручную.
Условия с этой вкладки попадают в секцию ГДЕ.
Параметры виртуальной таблицы
Для виртуальных таблиц Остатки, Обороты, СрезПоследних отбор лучше задавать не в ГДЕ, а в их параметрах. В конструкторе выделите таблицу в списке выбранных и нажмите «Параметры виртуальной таблицы». В поле «Условие» впишите отбор.
ВЫБРАТЬ
Остатки.Товар,
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.ОстаткиТоваров.Остатки(&НаДату, Склад = &Склад) КАК Остатки
Разница существенная. Условие в параметрах применяется до расчёта остатков. Условие в ГДЕ применяется к уже рассчитанной таблице по всем складам — на базе с двадцатью складами это лишняя работа в десятки раз.
В параметрах виртуальной таблицы используйте только измерения регистра. Ресурсы там недоступны, а условие по ресурсу КоличествоОстаток > 0 ставьте в ГДЕ.
Программное выполнение запроса
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Товар,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ОстаткиТоваров.Остатки(&НаДату, Склад = &Склад) КАК Остатки
|ГДЕ
| Остатки.КоличествоОстаток > 0";
Запрос.УстановитьПараметр("НаДату", КонецДня(ТекущаяДатаСеанса()));
Запрос.УстановитьПараметр("Склад", Склад);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Строка(Выборка.Товар) + ": " + Формат(Выборка.КоличествоОстаток, "ЧГ=0"));
КонецЦикла;
Связку «запрос + набор» применяют, когда нужно изменить записи независимого регистра сведений по неравенству. Запрос находит ключи нужных записей, а для каждого ключа создаётся свой набор с отбором по равенству.
Отбор через систему компоновки данных
Отбор СКД — третий вариант. Он работает в отчётах, динамических списках и везде, где есть компоновщик настроек. Виды сравнения здесь широкие: «равно», «в списке», «в иерархии», «больше», «содержит», «заполнено».
Отбор в отчёте
Отбор = КомпоновщикНастроек.Настройки.Отбор;
Элемент = Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
Элемент.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Склад");
Элемент.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
Элемент.ПравоеЗначение = Склад;
Элемент.Использование = Истина;
Имя в ПолеКомпоновкиДанных — это путь к полю набора данных схемы, а не имя измерения в метаданных. Если в схеме поле переименовано, используйте новое имя.
Отбор динамического списка
У динамического списка на форме тоже есть Отбор, устроенный так же. В типовых конфигурациях на БСП удобнее вызвать готовую процедуру:
ОбщегоНазначенияКлиентСервер.УстановитьЭлементОтбораДинамическогоСписка(
Список, "Склад", Склад, ВидСравненияКомпоновкиДанных.Равно, , ЗначениеЗаполнено(Склад));
Она находит существующий элемент отбора по полю и обновляет его, а не добавляет второй. При ручном коде повторный вызов Добавить создаст дубль условия, и список покажет пустоту, если значения разные.
Отбор по периоду в регистрах сведений и накопления
С периодом проще всего ошибиться, поэтому разберём его отдельно.
Периодический регистр сведений
Платформа приводит период записи к началу периода регистра. У регистра с периодичностью «Месяц» запись на 15.03 сохранится с датой 01.03. Отбор же сравнивает значение буквально. Поэтому для месячного регистра устанавливайте приведённую дату:
Набор.Отбор.Период.Установить(НачалоМесяца(Дата));
С датой 15.03 в отборе Прочитать() вернёт пустой набор, хотя запись за март есть.
Отбор по Период ставится только на одну дату. Нужен интервал — используйте запрос к таблице регистра с условием Период МЕЖДУ &Начало И &Конец или виртуальную таблицу СрезПоследних(&НаДату, ...).
Регистры накопления
У набора регистра накопления отбора по периоду нет вообще. Период задаётся у каждой записи, а набор связан с документом. Для выборки за интервал есть виртуальная таблица Обороты(&Начало, &Конец, , Склад = &Склад) и ОстаткиИОбороты.
Граница интервала включается в расчёт по моменту времени. Для конца дня передавайте КонецДня(Дата), иначе движения того дня после полуночи не попадут.
Производительность и типичные ошибки
Как не замедлить базу
- ✅ Ставьте отбор в параметры виртуальной таблицы, а не в
ГДЕ. - ✅ Пишите набор один раз после всех изменений, а не после каждой строки.
- ✅ Ограничивайте
Прочитать()полным отбором: набор без отбора по большому регистру загрузит в память всё. - ✅ В циклах по сотням ключей выбирайте данные одним запросом, а не сотней наборов с
Прочитать(). - ✅ Перед изменением наборов в транзакции ставьте управляемую блокировку по тем же полям:
БлокировкаДанныхсЭлементБлокировки.УстановитьЗначение("Товар", Товар).
Частые ошибки
Частые вопросы
Можно ли поставить в отбор набора записей список значений?
В (&Список).Чем отличается набор записей от менеджера записи?
Нужен ли отбор, если набор взят из коллекции Движения?
Движения уже отобраны по регистратору — текущему документу. Перезаписывать отбор не нужно.Как удалить одну запись регистра сведений кодом?
Записать() у пустого набора. Удалится только она.Почему у регистра сведений нет отбора по Регистратор?
Регистратор появляется, только если в свойствах регистра выбран режим записи «Подчинение регистратору».