При разработке механизмов проведения документов в 1С начинающие программисты часто сталкиваются с ситуацией, когда запрос, написанный в консоли и возвращающий данные, отказывается работать непосредственно в модуле объекта. Кроме того, неправильное использование запросов внутри транзакции проведения может привести к существенным задержкам в работе системы и блокировкам базы данных. В этой статье мы подробно разберем, почему не читаются данные из выборки, и как переписать код так, чтобы он соответствовал стандартам разработки 1С.
Рассмотрим ситуацию: вы написали правильный текст запроса, установили параметры и выполнили его. Однако при попытке обратиться к полю выборки, например Выборка.Код, система возвращает Неопределено или ошибку. Проанализируем фрагмент кода:
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
КодОтпр = ВыборкаДетальныеЗаписи.Код; // Ошибка или пустая ссылка!
Причина заключается в механизме работы объекта ВыборкаИзРезультатаЗапроса. Метод Выбрать() лишь инициализирует выборку и ставит указатель в позицию "перед первой записью". Для того чтобы получить доступ к данным первой строки, необходимо явно переместить указатель на эту строку.
Решение: всегда используйте метод Следующий(). Если вы ожидаете только одну запись, можно использовать условие Если, если записей несколько — цикл Пока.
ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
// Если нам нужна только одна (первая) запись
Если ВыборкаДетальныеЗаписи.Следующий() Тогда
КодОтпр = ВыборкаДетальныеЗаписи.Код;
// Дальнейшая логика обработки
КонецЕсли;
В исходном примере автор выполняет запрос для каждой строки табличной части документа. Рассмотрим, почему это плохо. Представьте, что в документе 100 строк. Это значит, что при проведении система 100 раз обратится к SQL-серверу, 100 раз скомпилирует план запроса и 100 раз выполнит поиск по таблицам. Это классическая проблема N+1, которая "убивает" производительность базы данных.
Проанализируем, как сделать правильно. Вместо того чтобы спрашивать базу данных по одной строке, мы должны собрать все необходимые данные из документа и получить все остатки одним пакетом.
Алгоритм правильного проведения:
РегистрНакопления.Отправка.Остатки).Рассмотрим пример реализации такого подхода:
// 1. Подготовка таблицы данных документа
ТаблицаДляЗапроса = ТабличнаяЧасть1.Выгрузить();
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеДокумента ИЗ &ТЗ КАК ТЗ;
|////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Товар,
| ВТ.ШтрихКод,
| ОтправкаОстатки.Пункт,
| ОтправкаОстатки.Маршрут,
| ОтправкаОстатки.ПлощадьОстаток
|ИЗ
| ВТ_ДанныеДокумента КАК ВТ
|ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Отправка.Остатки(&Период, ) КАК ОтправкаОстатки
|ПО ВТ.ШтрихКод = ОтправкаОстатки.Код";
Запрос.УстановитьПараметр("ТЗ", ТаблицаДляЗапроса);
Запрос.УстановитьПараметр("Период", Новый Граница(МоментВремени(), ВидГраницы.Исключая));
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Формируем движения на основе данных выборки
Движение = Движения.Отправка.ДобавитьРасход();
Движение.Период = Дата;
// ... заполнение реквизитов из выборки ...
КонецЦикла;
В коде автора встречается проверка: СокрЛП(ТекСтрока.Товар.Родитель) = "002_ИЗДЕЛИЯ". Проанализируем ситуацию с точки зрения системы. Когда вы пишете Товар.Родитель, 1С выполняет неявный запрос к таблице справочника "Товары", чтобы получить объект целиком, а затем еще один запрос, чтобы получить его родителя. Если у вас 1000 строк в документе, вы получите тысячи лишних обращений к СУБД.
Как оптимизировать: все поля, которые вам нужны для проверки условий (включая родителя, артикул, вид номенклатуры и т.д.), должны быть получены в основном запросе через соединение. Обращение "через точку" в циклах проведения — верный признак неоптимального кода.
Выясним причину, почему при получении остатков в обработке проведения важно использовать объект Граница. Если просто передать в запрос Дата документа, результат может быть непредсказуемым: система может учесть движения текущего документа, если он перепроводится, или не учесть их.
Чтобы получить "чистые" остатки на момент до записи текущего документа, мы используем конструкцию:
Новый Граница(МоментВремени(), ВидГраницы.Исключая)
Это гарантирует, что запрос вернет состояние регистра в ту микросекунду, когда документ еще не внес свои изменения.
Посмотрим на условие связи или фильтрации: ГДЕ ОтправкаОстатки.Код = &ШтК. Если поле Код в регистре является обычным реквизитом, по которому не построен индекс, то при поиске SQL-сервер будет вынужден просматривать всю таблицу регистра целиком. При больших объемах данных это приведет к фатальному замедлению проведения.
Рекомендация: Убедитесь, что поле, по которому идет поиск (в данном случае Код), является Измерением регистра (они индексируются автоматически) или у реквизита установлено свойство Индексировать.
Подводя итог, разберем основные правила написания кода в ОбработкаПроведения:
Выборка.Следующий(). Без этого метода данные из результата запроса недоступны.БлокировкаДанных перед выполнением запроса.Соблюдение этих принципов позволит вашим документам проводиться быстро, не мешая работе других пользователей и сохраняя целостность данных в системе.