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