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