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