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