СрезПоследних
Учимся брать «последнее известное значение на дату» — цену, курс, ставку — одной виртуальной таблицей вместо ручного перебора истории.
СрезПоследних — виртуальная таблица регистра сведений, которая для каждой комбинации измерений возвращает ровно одну, самую свежую запись на указанную дату (или на текущий момент, если дату не задавать).
Представьте регистр сведений Цены: он периодический, и в нём хранится история — 1 января телефон стоил 30000, 15 марта подорожал до 32000, 1 июня — до 35000. Когда мы оформляем документ 20 марта, нам нужна цена 32000: та, что действовала на эту дату. Перебирать всю историю руками и искать «последнюю запись не позже 20 марта» — долго и больно. Ровно эту работу за нас делает виртуальная таблица СрезПоследних.
Регистр сведений — таблица для хранения значений, привязанных к измерениям (и, если регистр периодический, к дате). Классика: цены номенклатуры, курсы валют, ставки НДС, ответственные по складам. Измерение — то, в разрезе чего хранятся данные (Номенклатура, Валюта). Ресурс — само хранимое значение (Цена, Курс).
Зачем это нужно на практике
«Актуальное значение на дату» — одна из самых частых задач в 1С. Цена в чеке, курс валюты в проводке, тариф в начислении, текущий ответственный по складу — всё это периодические регистры сведений, и почти всегда вам нужен срез на конкретный момент. Без виртуальной таблицы пришлось бы писать подзапрос с группировкой по максимальной дате и соединением — громоздко и легко ошибиться. СрезПоследних решает это одной строкой.
Синтаксис: имя таблицы и параметр Период
В запросе виртуальная таблица пишется как РегистрСведений.ИмяРегистра.СрезПоследних(<Период>, <Отбор>). Оба параметра необязательны, но Период — ключевой: это дата, на которую берётся срез.
ВЫБРАТЬ
ЦеныСрезПоследних.Номенклатура КАК Номенклатура,
ЦеныСрезПоследних.Цена КАК Цена
ИЗ
РегистрСведений.Цены.СрезПоследних(&ДатаСреза, ) КАК ЦеныСрезПоследних
Здесь &ДатаСреза — параметр запроса, который мы зададим из кода через УстановитьПараметр. Второй аргумент (отбор) оставлен пустым — вернутся цены на все товары.
Как задать дату из встроенного языка
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныСрезПоследних.Номенклатура КАК Номенклатура,
| ЦеныСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.Цены.СрезПоследних(&ДатаСреза, ) КАК ЦеныСрезПоследних";
Запрос.УстановитьПараметр("ДатаСреза", '20240320235959');
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Номенклатура.Наименование + ": " + Выборка.Цена);
КонецЦикла;
Результат:
Смартфон Alpha: 32000
Ноутбук Beta: 54900
Мышь Gamma: 990
Обратите внимание на литерал даты '20240320235959' — в 1С дата пишется без кавычек-строк, в апострофах, форматом ГГГГММДДЧЧММСС. Секунды до конца дня (235959) взяты специально: если товар подорожал ровно 20 марта, мы хотим захватить и эту новую цену, а не остаться на старой (об этой ловушке — в конце урока).
Отбор внутри виртуальной таблицы
Чаще всего цена нужна не по всем товарам, а по одному-двум. Второй параметр СрезПоследних — это отбор, и передавать условие лучше именно сюда, а не в ГДЕ (почему — отдельный урок раздела). Отбор пишется как условие с полями измерений регистра:
ВЫБРАТЬ
ЦеныСрезПоследних.Цена КАК Цена
ИЗ
РегистрСведений.Цены.СрезПоследних(
&ДатаСреза,
Номенклатура = &Товар
И ТипЦен = &РозничныйТип) КАК ЦеныСрезПоследних
Если у регистра несколько измерений (например, Номенклатура и ТипЦен), срез вернёт последнюю запись для каждой уникальной комбинации. Отбор по ТипЦен = &РозничныйТип оставит только розничные цены.
Классическая задача: цена на дату документа
Самый частый сценарий — при проведении документа подставить цену, актуальную на дату этого документа. Вот как это выглядит в модуле объекта:
Функция ПолучитьЦенуНаДату(Товар, ТипЦен, ДатаДок) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЦеныСрезПоследних.Цена КАК Цена
|ИЗ
| РегистрСведений.Цены.СрезПоследних(
| &ДатаДок,
| Номенклатура = &Товар
| И ТипЦен = &ТипЦен) КАК ЦеныСрезПоследних";
Запрос.УстановитьПараметр("ДатаДок", ДатаДок);
Запрос.УстановитьПараметр("Товар", Товар);
Запрос.УстановитьПараметр("ТипЦен", ТипЦен);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
Возврат Выборка.Цена;
Иначе
Возврат 0; // цена ещё не установлена
КонецЕсли;
КонецФункции
Функция вернёт цену, действовавшую на момент документа. Проверка Если Выборка.Следующий() обязательна: если для товара цену вообще ни разу не заводили, срез вернёт пустую выборку, и без проверки мы получим ошибку обращения к Выборка.Цена.
Как это работает
Под капотом СрезПоследних — это НЕ реальная таблица в базе. Платформа 1С в момент выполнения запроса разворачивает виртуальную таблицу в настоящий SQL-запрос к таблице регистра сведений: группирует записи по измерениям, находит максимальную дату не позже переданного Периода и подтягивает ресурсы именно этой записи. Проще говоря, за красивым коротким синтаксисом стоит сгенерированный подзапрос с MAX(Период) и соединением — но писать и оптимизировать его платформа берёт на себя.
Важное следствие: чем точнее отбор внутри виртуальной таблицы, тем меньше данных платформа поднимает из базы. Если отобрать один товар прямо в параметрах, СУБД отработает по индексу и вернёт одну запись. Если же вытащить срез по всем товарам, а потом отфильтровать в ГДЕ — база сначала посчитает срез по десяткам тысяч позиций и лишь затем отбросит лишнее.
Если Период не указать вовсе — РегистрСведений.Цены.СрезПоследних() — срез берётся на текущий момент (фактически на «конец времён»), то есть возвращаются самые последние из имеющихся значений. Это удобно для отчёта «текущие цены», но опасно внутри проведения документа задним числом: подставится не та цена, что была на дату документа, а самая свежая из справочника.
Частые ошибки
- Забыть про время в дате. Литерал
'20240320'— это20.03.2024 00:00:00, то есть самое начало дня. Если цена изменилась 20 марта в течение дня, срез на00:00:00вернёт вчерашнюю. Для «на весь день» берите конец дня:КонецДня(ДатаДок)в коде или'20240320235959'в тексте запроса. - Путать СрезПоследних и СрезПервых. У регистра сведений есть и
СрезПервых— она вернёт первую запись начиная с даты. Для «актуальной цены» нужен именно СрезПоследних; СрезПервых применяют реже (например, «первая цена после акции»). - Применять к непериодическому регистру. Виртуальные таблицы СрезПоследних/СрезПервых есть только у периодических регистров сведений. У непериодического их просто нет — платформа не даст обратиться к
.СрезПоследних(...). - Не проверять пустой результат. Если по товару значение не заводили, выборка пуста. Всегда оборачивайте чтение в
Если Выборка.Следующий() Тогда ... КонецЕсли;. - Тащить весь срез и фильтровать в ГДЕ. Условие по измерению кладите во второй параметр виртуальной таблицы, а не в
ГДЕ— это радикально быстрее (подробно в уроке «Параметры виртуальной таблицы vs ГДЕ»).
Итоги
- СрезПоследних — виртуальная таблица периодического регистра сведений: одна самая свежая запись на дату для каждой комбинации измерений.
- Первый параметр — Период (дата среза); без него срез берётся на текущий момент.
- Второй параметр — отбор по измерениям; кладите условия сюда, а не в ГДЕ.
- Классика — «цена/курс/ставка на дату документа»; всегда проверяйте пустую выборку.
- Помните про время в дате:
'20240320'— это начало дня; для всего дня беритеКонецДня(...).