Как правильно использовать запрос в обработке проведения и избежать типичных ошибок производительности?

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

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

Проблема 1: Почему выборка пустая или не читается?

Рассмотрим ситуацию: вы написали правильный текст запроса, установили параметры и выполнили его. Однако при попытке обратиться к полю выборки, например Выборка.Код, система возвращает Неопределено или ошибку. Проанализируем фрагмент кода:


РезультатЗапроса = Запрос.Выполнить();    
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
КодОтпр = ВыборкаДетальныеЗаписи.Код; // Ошибка или пустая ссылка!

Причина заключается в механизме работы объекта ВыборкаИзРезультатаЗапроса. Метод Выбрать() лишь инициализирует выборку и ставит указатель в позицию "перед первой записью". Для того чтобы получить доступ к данным первой строки, необходимо явно переместить указатель на эту строку.

Решение: всегда используйте метод Следующий(). Если вы ожидаете только одну запись, можно использовать условие Если, если записей несколько — цикл Пока.


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

// Если нам нужна только одна (первая) запись
Если ВыборкаДетальныеЗаписи.Следующий() Тогда
    КодОтпр = ВыборкаДетальныеЗаписи.Код;
    // Дальнейшая логика обработки
КонецЕсли;

Проблема 2: Запрос внутри цикла — главная ошибка производительности

В исходном примере автор выполняет запрос для каждой строки табличной части документа. Рассмотрим, почему это плохо. Представьте, что в документе 100 строк. Это значит, что при проведении система 100 раз обратится к SQL-серверу, 100 раз скомпилирует план запроса и 100 раз выполнит поиск по таблицам. Это классическая проблема N+1, которая "убивает" производительность базы данных.

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

Алгоритм правильного проведения:

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

Рассмотрим пример реализации такого подхода:


// 1. Подготовка таблицы данных документа
ТаблицаДляЗапроса = ТабличнаяЧасть1.Выгрузить();

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеДокумента ИЗ &ТЗ КАК ТЗ;
    |////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ.Товар,
    |    ВТ.ШтрихКод,
    |    ОтправкаОстатки.Пункт,
    |    ОтправкаОстатки.Маршрут,
    |    ОтправкаОстатки.ПлощадьОстаток
    |ИЗ
    |    ВТ_ДанныеДокумента КАК ВТ
    |ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Отправка.Остатки(&Период, ) КАК ОтправкаОстатки
    |ПО ВТ.ШтрихКод = ОтправкаОстатки.Код";

Запрос.УстановитьПараметр("ТЗ", ТаблицаДляЗапроса);
Запрос.УстановитьПараметр("Период", Новый Граница(МоментВремени(), ВидГраницы.Исключая));

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

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

Проблема 3: Разыменование ссылок через точку

В коде автора встречается проверка: СокрЛП(ТекСтрока.Товар.Родитель) = "002_ИЗДЕЛИЯ". Проанализируем ситуацию с точки зрения системы. Когда вы пишете Товар.Родитель, 1С выполняет неявный запрос к таблице справочника "Товары", чтобы получить объект целиком, а затем еще один запрос, чтобы получить его родителя. Если у вас 1000 строк в документе, вы получите тысячи лишних обращений к СУБД.

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

Проблема 4: Установка границы остатков

Выясним причину, почему при получении остатков в обработке проведения важно использовать объект Граница. Если просто передать в запрос Дата документа, результат может быть непредсказуемым: система может учесть движения текущего документа, если он перепроводится, или не учесть их.

Чтобы получить "чистые" остатки на момент до записи текущего документа, мы используем конструкцию:

Новый Граница(МоментВремени(), ВидГраницы.Исключая)

Это гарантирует, что запрос вернет состояние регистра в ту микросекунду, когда документ еще не внес свои изменения.

Проблема 5: Индексация полей

Посмотрим на условие связи или фильтрации: ГДЕ ОтправкаОстатки.Код = &ШтК. Если поле Код в регистре является обычным реквизитом, по которому не построен индекс, то при поиске SQL-сервер будет вынужден просматривать всю таблицу регистра целиком. При больших объемах данных это приведет к фатальному замедлению проведения.

Рекомендация: Убедитесь, что поле, по которому идет поиск (в данном случае Код), является Измерением регистра (они индексируются автоматически) или у реквизита установлено свойство Индексировать.

Итоги и лучшие практики

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

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

← На главную