Как получить Регистратор в запросе к регистру накопления в 1С: разбор ошибок и оптимизация

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При работе с механизмами регистров в платформе «1С:Предприятие» разработчики часто сталкиваются с ситуацией, когда при обращении к результатам запроса возникает ошибка «Поле объекта не обнаружено (Регистратор)». Это классическая проблема, которая может быть вызвана как банальной невнимательностью при написании текста запроса, так и непониманием принципов работы виртуальных таблиц СУБД в 1С. Рассмотрим эту проблему подробно, разберем причины ее возникновения и выясним, как правильно и оптимально получать документ-регистратор в запросах.

Анализ исходной ошибки: невнимательность разработчика

Для начала проанализируем ситуацию, описанную автором темы. Разработчик пытается выполнить запрос к виртуальной таблице оборотов регистра накопления РегистрНакопления.УчетТабеля.Обороты и получить из нее данные о сотруднике, сумме оборота и регистраторе. Однако при обходе выборки в коде:

Часы = (ТекущаяДата() - Выб.Регистратор.Дата) / 3600;

платформа выдает ошибку о том, что поле не обнаружено. Посмотрим на текст запроса, который написал автор:


Запрос.Текст = 
    "ВЫБРАТЬ
    |    УчетТабеляОбороты.Сотр КАК Сотр,
    |    УчетТабеляОбороты.СуммаОборот 
    |//  |    УчетТабеляОбороты.Регистратор как Регистратор 
    |ИЗ
    |    РегистрНакопления.УчетТабеля.Обороты(&Дата1,&Дата2,Регистратор) КАК УчетТабеляОбороты
    |ГДЕ
    |    УчетТабеляОбороты.Сотр = &Сотр";

Причина ошибки лежит на поверхности: строка выбора поля Регистратор была закомментирована символами двунаправленного слеша // в теле запроса. Несмотря на то, что в параметрах виртуальной таблицы третьим параметром указана периодичность Регистратор, само поле не было добавлено в секцию ВЫБРАТЬ.

Правило простое: если мы хотим обратиться к какому-либо полю в выборке результата запроса (например, через ВыборкаДетальныеЗаписи.Регистратор), это поле в обязательном порядке должно быть перечислено в секции ВЫБРАТЬ нашего запроса.

Настройка периодичности виртуальной таблицы

Рассмотрим подробнее, как связаны параметры виртуальной таблицы и поля, которые мы можем выбирать. Третий параметр виртуальных таблиц Обороты или ОстаткиИОбороты отвечает за периодичность данных.

Мы можем использовать следующие значения периодичности:

  1. Период — данные группируются за весь указанный в параметрах интервал.
  2. Год, Полугодие, Квартал, Месяц, Декада, Неделя, День — данные будут сгруппированы по соответствующим календарным периодам.
  3. Регистратор — данные детализируются до конкретного документа, который сделал движение по регистру.
  4. Запись — максимальная детализация (до конкретной строки в наборе записей регистра).
  5. Авто — автоматическая периодичность. Платформа сама определяет степень детализации на основе тех полей периодов, которые разработчик указал в секции ВЫБРАТЬ.

Если мы хотим получить поле Регистратор, то в параметрах виртуальной таблицы мы обязаны указать периодичность Регистратор, Запись или Авто. Если оставить периодичность по умолчанию (без указания параметра), платформа сгруппирует данные за весь период, и поле Регистратор физически не сможет быть получено (оно будет равно Неопределено или вызовет ошибку еще на этапе компиляции запроса).

Пример правильного запроса с получением регистратора:


ВЫБРАТЬ
    УчетТабеляОбороты.Сотр КАК Сотр,
    УчетТабеляОбороты.Регистратор КАК Регистратор,
    УчетТабеляОбороты.СуммаОборот КАК СуммаОборот
ИЗ
    РегистрНакопления.УчетТабеля.Обороты(&Дата1, &Дата2, Регистратор, ) КАК УчетТабеляОбороты

Влияние периодичности «Регистратор» на производительность СУБД

Проанализируем архитектурные особенности работы платформы 1С при выполнении таких запросов. Использование периодичности Регистратор в виртуальных таблицах — это операция, требующая повышенного внимания из-за возможного падения производительности.

Под капотом платформы «1С:Предприятие» для каждого регистра накопления существуют физические таблицы:

Когда мы запрашиваем данные из виртуальной таблицы Обороты без указания периодичности или с периодичностью Месяц, СУБД обращается к готовой таблице итогов. Это происходит мгновенно, так как данные там уже сгруппированы и подсчитаны.

Но как только мы указываем периодичность Регистратор, платформа не может использовать таблицу предопределенных итогов. Ей приходится считывать данные напрямую из огромной таблицы движений за весь указанный период, выполнять динамическое соединение с таблицами документов и производить группировку «на лету». На базах данных большого объема такой запрос может привести к серьезным задержкам и блокировкам таблиц.

Альтернативный вариант: запрос к физической таблице движений

Если нам нужно просто получить список документов, сделавших движения по регистру за период с отбором по измерению, зачастую гораздо эффективнее сделать запрос напрямую к физической таблице регистра накопления, минуя механизмы виртуальных таблиц.

Разберем пример такого запроса:


ВЫБРАТЬ
    УчетТабеля.Сотр КАК Сотр,
    УчетТабеля.Регистратор КАК Регистратор,
    УчетТабеля.Сумма КАК СуммаОборот
ИЗ
    РегистрНакопления.УчетТабеля КАК УчетТабеля
ГДЕ
    УчетТабеля.Сотр = &Сотр
    И УчетТабеля.Период МЕЖДУ &Дата1 И &Дата2

Почему это работает быстрее? Этот запрос не задействует сложные математические алгоритмы расчета остатков и оборотов на границах периодов, которые встроены в виртуальные таблицы. База данных просто выполняет выборку по физическому индексу таблицы (который в 1С автоматически строится по измерениям и периоду).

Особенности работы с виртуальной таблицей «ОстаткиИОбороты»

Если бы мы решали аналогичную задачу с регистром остатков через таблицу ОстаткиИОбороты, то при использовании периодичности Регистратор мы могли бы столкнуться с проблемой значений NULL.

Если за выбранный период по измерению не было движений, но на начало периода существовал остаток, таблица все равно вернет строку с начальным остатком. Однако, поскольку движений в периоде не происходило, поле Регистратор для этой строки примет значение NULL. Обращение в коде к свойствам такого поля (например, Выборка.Регистратор.Дата) гарантированно вызовет ошибку выполнения.

Для предотвращения подобных ситуаций в запросах к таблице ОстаткиИОбороты обязательно используйте функцию ЕСТЬNULL:


ВЫБРАТЬ
    ЕСТЬNULL(ОстаткиОбороты.Регистратор, НЕОПРЕДЕЛЕНО) КАК Регистратор
ИЗ
    РегистрНакопления.Взаиморасчеты.ОстаткиИОбороты(&Начало, &Конец, Регистратор, , ) КАК ОстаткиОбороты

Рефакторинг и оптимизация исходного кода автора

Посмотрим еще раз на обработчик ПередЗаписью в сообщении автора темы. В нем содержатся две критические ошибки проектирования, которые недопустимы в реальных рабочих конфигурациях.

  1. Использование интерактивных методов в транзакции: Вызов метода Таб3.ВыбратьСтроку(); открывает модальное окно прямо во время записи документа. Процедура ПередЗаписью выполняется внутри неявной транзакции базы данных. Открытие любых диалоговых окон в транзакции категорически запрещено стандартом разработки 1С. Это приводит к блокировке таблиц СУБД для всех пользователей базы данных до тех пор, пока текущий пользователь не закроет окно, а в режиме веб-клиента или фонового задания просто вызовет аварийное завершение работы системы.
  2. Обращение к реквизитам через точку (разыменование ссылок): Конструкция Выб.Регистратор.Дата в цикле или выборке заставляет платформу выполнять отдельный неявный SQL-запрос к базе данных для каждого обрабатываемого документа, чтобы получить его дату. Если в выборке будет 1000 строк, система сделает 1000 дополнительных микро-запросов к СУБД, что сильно снизит скорость работы системы.

Выясним, как сделать этот код правильным, быстрым и безопасным. Перенесем получение даты регистратора непосредственно в сам запрос, а также избавимся от отладочных интерактивных вызовов.

Рассмотрим оптимизированный вариант обработчика:


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

Подведем итог нашему разбору. Мы не только нашли банальную синтаксическую ошибку автора с закомментированным полем, но и детально изучили поведение платформы при получении регистратора. Соблюдение этих простых правил написания запросов и работы с транзакциями позволит вашим решениям на платформе 1С работать быстро, стабильно и без непредвиденных блокировок.

← На главную