Как оптимально создать регистр сведений для очистки резервов и эффективно работать с ним в 1С?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При создании и работе с регистрами сведений в 1С:Предприятии важно правильно понимать роль каждого метаданного – измерения, ресурса и реквизита. От их корректного определения зависит не только логика хранения данных, но и производительность запросов, а также общая стабильность системы. В рамках этой статьи мы рассмотрим, как создать оптимальный регистр сведений для управления очередью очистки резервов, правильно построить запросы к нему и безопасно удалять записи, избегая распространенных ошибок.

Понимание Измерений, Ресурсов и Реквизитов в 1С

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

  1. Измерения:
  2. Ресурсы:
  3. Реквизиты:

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

Оптимальная структура регистра сведений для очистки резервов

Исходя из поставленной задачи – создания "пула очистки резервов на складе поля Заказ покупателя + ДатаОкончания резерва", с дальнейшим поиском по условию "дата меньше чем резерв до" и "ЗаказПокупателя = заданный", мы можем предложить следующую оптимальную структуру регистра сведений:

Название регистра: ОчередьУдаленияРезервов

  1. Измерения:
  2. Ресурсы:
  3. Реквизиты:

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

Оптимизация запросов к регистру сведений

После того как мы определили структуру регистра, давайте рассмотрим, как создавать эффективные запросы к нему. Наша задача – выбрать записи, где ДатаОкончанияРезерва меньше заданной даты и связанный ЗаказПокупателя совпадает с указанным.

В начале обсуждения была предложена такая конструкция запроса:


Процедура УдалитьЗначениеРегистраОчередьУдаленияРезервов(ДатаРезервДоВключительно, ЗаказПокупателя)
    "ВЫБРАТЬ
    |    ОчередьУдаленияРезервов.РезервДоВключительно,
    |    ОчередьУдаленияРезервов.ЗаказПокупателя
    |ИЗ
    |    РегистрСведений.ОчередьУдаленияРезервов КАК ОчередьУдаленияРезервов
    |ГДЕ
    |    ОчередьУдаленияРезервов.РезервДоВключительно < &РезервДоВключительно
    |    И ОчередьУдаленияРезервов.ЗаказПокупателя = &ЗаказПокупателяУсловие
    |
    |СГРУППИРОВАТЬ ПО
    |    ОчередьУдаленияРезервов.РезервДоВключительно,
    |    ОчередьУдаленияРезервов.ЗаказПокупателя"
КонецПроцедуры

Мы видим, что в этом запросе присутствует оператор СГРУППИРОВАТЬ ПО, но отсутствуют агрегатные функции (такие как СУММА, КОЛИЧЕСТВО, МАКСИМУМ и т.д.). В таких случаях использование СГРУППИРОВАТЬ ПО избыточно и может немного замедлять выполнение запроса. Если нам нужны уникальные комбинации полей без агрегации, гораздо эффективнее использовать оператор РАЗЛИЧНЫЕ.

Оптимизированный запрос будет выглядеть так:


"ВЫБРАТЬ РАЗЛИЧНЫЕ
|    ОчередьУдаленияРезервов.ДатаОкончанияРезерва,
|    ОчередьУдаленияРезервов.ЗаказПокупателя
|ИЗ
|    РегистрСведений.ОчередьУдаленияРезервов КАК ОчередьУдаленияРезервов
|ГДЕ
|    ОчередьУдаленияРезервов.ДатаОкончанияРезерва < &ДатаРезервДоВключительно
|    И ОчередьУдаленияРезервов.ЗаказПокупателя = &ЗаказПокупателяУсловие"

Общие правила оптимизации запросов в 1С:

  1. Используйте ГДЕ по измерениям: Мы уже обсудили, что измерения индексируются по умолчанию. Всегда старайтесь строить условия отбора (`ГДЕ`) по полям, являющимся измерениями. Это позволяет СУБД максимально эффективно использовать индексы и быстро находить нужные данные.
  2. Избегайте функций в условиях ГДЕ: Не применяйте функции к полям, по которым идет отбор в условии `ГДЕ`. Например, вместо ГОД(ДатаРезерва) = 2024 используйте диапазон дат: ДатаРезерва МЕЖДУ &НачалоГода И &КонецГода. Применение функции к полю приводит к полному сканированию таблицы, так как индекс не может быть использован для вычисленного значения.
  3. Минимизируйте поля в выборке: Выбирайте только те поля, которые вам действительно нужны для дальнейшей работы. Избегайте выбора всех полей (`*`), если это не необходимо, так как это увеличивает объем данных, передаваемых из базы данных, и может замедлить запрос.
  4. Операторы сравнения: Для числовых и строковых полей используйте точное сравнение (`=`). Для дат и чисел диапазонные сравнения (`<`, `>`, `<=`, `>=`) также хорошо работают с индексами.

Корректное удаление записей из регистра сведений

В процессе работы с регистром, особенно с очередями, часто возникает необходимость удаления записей. Автор столкнулся с распространенной ошибкой: "Выборка.ПолучитьМенеджерЗаписи ошибка не могу удалить элемент в цикле запроса". Давайте разберем причину этой ошибки и предложим безопасные методы удаления.

Почему возникает ошибка "не могу удалить элемент в цикле запроса"?

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

Правильные подходы к удалению записей

Мы рассмотрим два основных, безопасных и эффективных способа удаления записей из регистра сведений.

1. Сбор ключей для удаления, затем удаление

Этот метод заключается в том, что сначала мы выполняем запрос, чтобы получить уникальные идентификаторы (ключи) тех записей, которые хотим удалить. Затем, после завершения выборки, мы используем эти собранные ключи для поочередного удаления записей.

Шаги реализации:

  1. Создаем список или массив для хранения ключей записей, которые нужно удалить.
  2. Выполняем запрос для получения значений измерений (ключей) тех записей, которые удовлетворяют условию удаления.
  3. В цикле по результату запроса формируем менеджер записи для каждой записи и добавляем его в наш список.
  4. После завершения выборки (когда запрос больше не активен), итерируемся по собранному списку ключей и для каждого ключа вызываем метод Удалить().

Пример кода:


Процедура УдалитьПросроченныеРезервы(ДатаРезервДоВключительно, ЗаказПокупателя)

    СписокДляУдаления = Новый СписокЗначений;

    // 1. Выбираем ключи записей, которые необходимо удалить
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |    ОчередьУдаленияРезервов.ЗаказПокупателя,
        |    ОчередьУдаленияРезервов.ДатаОкончанияРезерва
        |ИЗ
        |    РегистрСведений.ОчередьУдаленияРезервов КАК ОчередьУдаленияРезервов
        |ГДЕ
        |    ОчередьУдаленияРезервов.ДатаОкончанияРезерва < &ДатаРезервДоВключительно
        |    И ОчередьУдаленияРезервов.ЗаказПокупателя = &ЗаказПокупателяУсловие";

    Запрос.УстановитьПараметр("ДатаРезервДоВключительно", ДатаРезервДоВключительно);
    Запрос.УстановитьПараметр("ЗаказПокупателяУсловие", ЗаказПокупателя);

    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        МенеджерЗаписи = РегистрыСведений.ОчередьУдаленияРезервов.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.ЗаказПокупателя = Выборка.ЗаказПокупателя;
        МенеджерЗаписи.ДатаОкончанияРезерва = Выборка.ДатаОкончанияРезерва;
        // Если есть другие измерения, их тоже нужно установить
        // МенеджерЗаписи.Номенклатура = Выборка.Номенклатура; 
        // МенеджерЗаписи.Склад = Выборка.Склад; 
        СписокДляУдаления.Добавить(МенеджерЗаписи);
    КонецЦикла;

    // 2. Удаляем записи по собранным ключам
    НачатьТранзакцию(); // Рекомендуется для операций с большим количеством данных
    Попытка
        Для Каждого КлючЗаписиИзСписка Из СписокДляУдаления Цикл
            КлючЗаписиИзСписка.Удалить();
        КонецЦикла;
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ВызватьИсключение "Ошибка при удалении записей: " + ОписаниеОшибки();
    КонецПопытки;

КонецПроцедуры
2. Удаление с использованием НабораЗаписей

Метод с использованием объекта НаборЗаписей является более эффективным для удаления большого объема данных, особенно если условие отбора для удаления может быть задано по измерениям регистра.

Шаги реализации:

  1. Создаем новый объект НаборЗаписей для нашего регистра сведений.
  2. Устанавливаем отборы для набора записей по тем измерениям, по которым мы можем сузить круг искомых данных (например, по ЗаказПокупателя).
  3. Вызываем метод Прочитать() для объекта НаборЗаписей. Это загрузит в память все записи, соответствующие установленным отборам.
  4. Итерируемся по загруженному набору записей. Для каждой записи проверяем дополнительное условие (например, ДатаОкончанияРезерва < &ДатаРезервДоВключительно).
  5. Если запись удовлетворяет условию, вызываем метод Удалить() для этой записи внутри набора.
  6. После того как все нужные записи удалены из набора в памяти, вызываем метод Записать() для объекта НаборЗаписей. Это применит все изменения (удаления) к базе данных.

Пример кода:


Процедура УдалитьПросроченныеРезервыНабором(ДатаРезервДоВключительно, ЗаказПокупателя)

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

        // Устанавливаем отбор по измерению, чтобы прочитать только релевантные записи
        НаборЗаписей.Отбор.ЗаказПокупателя.Установить(ЗаказПокупателя);
        // Если есть другие измерения, по которым можно сделать отбор до чтения, их тоже устанавливаем
        // Например, если ЗаказПокупателя имеет много номенклатуры, и нам нужны только резервы для определенного склада:
        // НаборЗаписей.Отбор.Склад.Установить(МойСклад);

        НаборЗаписей.Прочитать(); // Загружаем записи в набор в памяти

        // Итерируемся по загруженному набору и удаляем записи, соответствующие условию
        Для каждого ЗаписьРегистра Из НаборЗаписей Цикл
            Если ЗаписьРегистра.ДатаОкончанияРезерва < ДатаРезервДоВключительно Тогда
                НаборЗаписей.Удалить(ЗаписьРегистра); // Удаляем запись из набора в памяти
            КонецЕсли;
        КонецЦикла;

        НаборЗаписей.Записать(); // Применяем изменения (удаления) к базе данных
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ВызватьИсключение "Ошибка при удалении записей: " + ОписаниеОшибки();
    КонецПопытки;

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

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

Привязка регистра к проводке документа

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

Преимущества подчинения регистратору:

  1. Автоматическое управление записями: При перепроведении документа все старые записи, созданные им, автоматически удаляются, а новые создаются. При отмене проведения документа все записи, связанные с ним, также автоматически удаляются.
  2. Целостность данных: Обеспечивается строгая связь данных регистра с документом, что помогает поддерживать консистентность информации.

В нашем случае, если регистр ОчередьУдаленияРезервов используется как вспомогательный механизм для фонового удаления и не является "первичным" хранилищем резервов, его подчинение регистратору может быть необязательным. Однако, если записи в эту очередь создаются непосредственно при проведении документа ЗаказПокупателя, то рассмотрение этой опции может быть полезным для упрощения логики.

Заключение

Мы подробно рассмотрели, как эффективно создать и управлять регистром сведений для очистки резервов в 1С. Ключевыми моментами являются правильное определение измерений для обеспечения высокой производительности запросов, оптимизация запросов с использованием РАЗЛИЧНЫЕ и корректных условий ГДЕ, а также безопасные методы удаления записей из регистра с помощью сбора ключей или объекта НаборЗаписей. Применение этих подходов позволит создать надежную и производительную подсистему для управления резервами в вашей конфигурации 1С.

← На главную