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