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