При работе с регистрами сведений в 1С разработчики часто сталкиваются со стандартным поведением виртуальной таблицы СрезПоследних. Эта таблица возвращает последние записи в разрезе всех существующих измерений. Однако на практике возникают ситуации, когда нам нужно получить самую свежую запись по какому-то одному ключевому измерению (например, по СерияНоменклатуры), полностью игнорируя другие (например, ВидСертификата). В этом случае стандартная виртуальная таблица вернет несколько строк для одной серии, если для нее были записи с разными видами сертификатов, что нарушит логику отчета или обработки.
В этой статье мы подробно разберем, как обойти это ограничение, рассмотрим классические и современные методы решения данной задачи, а также проанализируем нюансы производительности и проблему «одинаковых секунд».
Представим регистр сведений СертификацияНоменклатуры. У него есть два измерения: СерияНоменклатуры и ВидСертификата. Если мы обратимся к виртуальной таблице СрезПоследних, система будет искать последнюю запись для каждой уникальной комбинации этих двух полей. Если для одной и той же серии вчера ввели «Сертификат соответствия», а сегодня «Пожарный сертификат», то срез последнего вернет обе эти записи. Если же наша задача — просто узнать, когда была любая последняя активность по серии, такой результат нам не подходит.
Рассмотрим по шагам, как получить действительно последнюю запись, используя только физические таблицы регистра.
Проанализируем самый надежный и распространенный способ. Его суть заключается в двухэтапном получении данных. Сначала мы находим максимальный период для нужного нам измерения, а затем «подтягиваем» остальные данные по этой связке.
Шаг 1: Получение максимального периода
Создадим временную таблицу, в которой сгруппируем данные по интересующему нас измерению и найдем МАКСИМУМ(Период).
ВЫБРАТЬ
Сертификация.СерияНоменклатуры КАК СерияНоменклатуры,
МАКСИМУМ(Сертификация.Период) КАК МаксПериод
ПОМЕСТИТЬ ВТ_МаксимальныеДаты
ИЗ
РегистрСведений.СертификацияНоменклатуры КАК Сертификация
ГДЕ
Сертификация.Период <= &ПериодГраница
СГРУППИРОВАТЬ ПО
Сертификация.СерияНоменклатуры
;
Шаг 2: Получение полных данных
Теперь мы соединяем нашу временную таблицу с основной физической таблицей регистра по двум полям: СерияНоменклатуры и Период.
ВЫБРАТЬ
ВТ.СерияНоменклатуры,
Сертификация.ВидСертификата,
Сертификация.НомерСертификата,
ВТ.МаксПериод КАК Период
ИЗ
ВТ_МаксимальныеДаты КАК ВТ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.СертификацияНоменклатуры КАК Сертификация
ПО ВТ.СерияНоменклатуры = Сертификация.СерияНоменклатуры
И ВТ.МаксПериод = Сертификация.Период
Важный нюанс: Если в одну и ту же секунду (один период) были записаны два разных сертификата для одной серии, этот запрос все равно вернет две строки. Чтобы этого избежать, нужно использовать дополнительную детализацию, о которой мы поговорим ниже.
Выясним причину, по которой предыдущий метод может выдавать дубли. В 1С Период имеет точность до секунды. В высоконагруженных системах вполне возможна ситуация, когда в одну секунду по одному измерению записываются разные данные. Чтобы гарантированно получить одну (последнюю) запись, необходимо использовать МоментВремени или комбинацию Регистратор + НомерСтроки.
Разберем, как модифицировать запрос для обеспечения уникальности:
МАКСИМУМ(Период) для каждой серии.МАКСИМУМ(МоментВремени) (в запросе это можно реализовать через соединение с физической таблицей и группировку по регистратору).В версии 8.3 для периодических регистров сведений, подчиненных регистратору, вместо МоментВремени в запросах часто используют связку Период, Регистратор.МоментВремени.
Рассмотрим наиболее современный и эффективный способ. Если ваша платформа поддерживает оконные функции, вам не нужно создавать временные таблицы и делать лишние соединения. Функция ROW_NUMBER() позволяет пронумеровать строки внутри каждой группы серий, отсортировав их по времени.
Проанализируем пример запроса:
ВЫБРАТЬ
Подзапрос.СерияНоменклатуры,
Подзапрос.ВидСертификата,
Подзапрос.Период
ИЗ (
ВЫБРАТЬ
Регистр.СерияНоменклатуры,
Регистр.ВидСертификата,
Регистр.Период,
НОМЕР_СТРОКИ() ПЕРЕОПРЕДЕЛИТЬ (
ПАРТИЦИЯ ПО Регистр.СерияНоменклатуры
УПОРЯДОЧИТЬ ПО Регистр.Период УБЫВ, Регистр.МоментВремени УБЫВ
) КАК НомерЗаписи
ИЗ
РегистрСведений.СертификацияНоменклатуры КАК Регистр
) КАК Подзапрос
ГДЕ
Подзапрос.НомерЗаписи = 1
Почему это лучше: СУБД выполняет этот запрос за один проход по индексу. Нам не нужно дважды сканировать таблицу регистра, что значительно повышает производительность на больших объемах данных.
Иногда встречается решение, где прямо в условии ГДЕ основного запроса прописывается подзапрос для получения максимального периода. Посмотрим на пример:
ВЫБРАТЬ
Р1.СерияНоменклатуры,
Р1.ВидСертификата
ИЗ
РегистрСведений.СертификацияНоменклатуры КАК Р1
ГДЕ
Р1.Период = (
ВЫБРАТЬ МАКСИМУМ(Р2.Период)
ИЗ РегистрСведений.СертификацияНоменклатуры КАК Р2
ГДЕ Р2.СерияНоменклатуры = Р1.СерияНоменклатуры
)
Предупреждение: Этот метод часто называют «выстрелом в ногу». Оптимизаторы SQL в 1С часто не могут эффективно обработать такую конструкцию, и запрос начинает выполняться построчно (Nested Loops). На таблице в 100 000 записей такой запрос может «повесить» систему. Используйте его только на очень маленьких объемах данных или если другие методы недоступны.
Для того чтобы срез последнего без учета измерения работал быстро, придерживайтесь следующих правил:
СерияНоменклатуры), является первым в каком-либо индексе регистра.РегистрСведений.ИмяРегистра. Виртуальная таблица СрезПоследних сама по себе является подзапросом с группировками, и строить над ней еще одну группировку — значит заставлять сервер делать двойную работу.Мы рассмотрели несколько способов получения среза последнего без учета одного измерения. Самым универсальным остается Метод 1 с использованием временных таблиц. Если же вы работаете на актуальных версиях платформы (8.3.21+), Метод 3 с оконными функциями будет самым лаконичным и быстрым. Всегда помните о возможности дублирования записей в одну секунду и добавляйте в условия соединения или сортировки дополнительные поля (Момент времени, Регистратор), чтобы результат всегда был однозначным.