Как в запросе 1С получить последнюю запись периодического регистра сведений для каждого элемента справочника?

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

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

В этой статье мы подробно разберем, как правильно решать эту классическую задачу. Мы детально проанализируем базовый метод с использованием временных таблиц, научимся элегантно обходить проблему совпадения периодов (коллизии в пределах одной секунды) и рассмотрим современные альтернативные подходы, включая интервальный метод и оптимизацию запросов под СУБД PostgreSQL.

Метод 1. Классический алгоритм с использованием временных таблиц

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

Представим, что у нас есть справочник Справочник.Номенклатура и периодический регистр сведений РегистрСведений.ЦеныНоменклатуры (измерения: Номенклатура, ТипЦен; ресурс: Цена). Нам необходимо получить последнюю установленную цену для каждой номенклатурной позиции.

Выполним эту задачу по шагам:

  1. Подготовка исходного списка объектов. Сначала мы выберем все интересующие нас элементы справочника во временную таблицу. Это позволит нам сузить выборку и ускорить работу последующих пакетов запроса.
  2. Поиск максимального периода. Во второй временной таблице мы обратимся к физической таблице регистра сведений. Сгруппировав данные по объекту (в нашем случае — номенклатуре), мы найдем максимальное значение поля Период.
  3. Получение целевых данных (ресурсов). В финальном запросе мы соединим таблицу максимальных дат с исходной физической таблицей регистра по ключевым полям: объекту и периоду. Это позволит однозначно вытащить ресурс (цену), соответствующий этой дате.

Проанализируем пример реализации этого алгоритма в конструкторе запросов:


// Шаг 1: Отбираем нужную номенклатуру
ВЫБРАТЬ
    Номенклатура.Ссылка КАК Номенклатура
ПОМЕСТИТЬ ВТ_ИсходнаяНоменклатура
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    НЕ Номенклатура.ЭтоГруппа
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Ищем максимальный период в регистре для каждого элемента
ВЫБРАТЬ
    Цены.Номенклатура КАК Номенклатура,
    МАКСИМУМ(Цены.Период) КАК МаксПериод
ПОМЕСТИТЬ ВТ_МаксимальныеДаты
ИЗ
    РегистрСведений.ЦеныНоменклатуры КАК Цены
ГДЕ
    Цены.Номенклатура В (ВЫБРАТЬ ВТ.Номенклатура ИЗ ВТ_ИсходнаяНоменклатура КАК ВТ)
ГРУППИРОВАТЬ ПО
    Цены.Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Соединяем максимальные даты с физической таблицей регистра для получения цены
ВЫБРАТЬ
    ВТ_ИсходнаяНоменклатура.Номенклатура КАК Номенклатура,
    ЕСТЬNULL(ВТ_МаксимальныеДаты.МаксПериод, ДАТАВРЕМЯ(1, 1, 1)) КАК ПериодЗаписи,
    ЕСТЬNULL(РеальныеЦены.Цена, 0) КАК АктуальнаяЦена
ИЗ
    ВТ_ИсходнаяНоменклатура КАК ВТ_ИсходнаяНоменклатура
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_МаксимальныеДаты КАК ВТ_МаксимальныеДаты
        ПО ВТ_ИсходнаяНоменклатура.Номенклатура = ВТ_МаксимальныеДаты.Номенклатура
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК РеальныеЦены
        ПО ВТ_МаксимальныеДаты.Номенклатура = РеальныеЦены.Номенклатура
        И ВТ_МаксимальныеДаты.МаксПериод = РеальныеЦены.Период

Важное примечание: Обратите внимание, что в финальном пакете мы используем именно ЛЕВОЕ СОЕДИНЕНИЕ. Это гарантирует, что в результирующую выборку попадут абсолютно все элементы справочника, даже те, по которым в регистре сведений еще ни разу не производилась запись цен.

Решение проблемы дубликатов на одну и ту же секунду (Коллизии времени)

На практике разработчики часто сталкиваются с неприятной ситуацией: что делать, если в периодическом регистре сведений две разные записи по одному и тому же объекту имеют абсолютно одинаковый период (вплоть до секунды)?

Выясним причину возникновения такой проблемы. Она напрямую зависит от режима записи регистра:

Для однозначного определения «самой последней» записи в подчиненном регистре нам нужно опираться на триаду полей, определяющих уникальность и хронологическую последовательность в 1С:

  1. Период — дата и время совершения движения.
  2. Регистратор — ссылка на документ-регистратор. В платформе 1С сравнение ссылок документов при определении максимума происходит по их «Моменту времени» (учитывается дата, время и уникальный идентификатор ссылки для разрешения коллизий).
  3. НомерСтроки — уникальный порядковый номер записи внутри конкретного документа-регистратора.

Давайте посмотрим на пример запроса, который гарантированно исключает дублирование записей при совпадении периодов:


// Шаг 1: Находим максимальную дату (период)
ВЫБРАТЬ
    Регистр.Номенклатура КАК Номенклатура,
    МАКСИМУМ(Регистр.Период) КАК МаксПериод
ПОМЕСТИТЬ ВТ_МаксПериод
ИЗ
    РегистрСведений.ЦеныНоменклатуры КАК Регистр
ГРУППИРОВАТЬ ПО
    Регистр.Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Находим максимальный регистратор для найденного периода
ВЫБРАТЬ
    Регистр.Номенклатура КАК Номенклатура,
    ВТ.МаксПериод КАК Период,
    МАКСИМУМ(Регистр.Регистратор) КАК МаксРегистратор
ПОМЕСТИТЬ ВТ_МаксРегистратор
ИЗ
    ВТ_МаксПериод КАК ВТ
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Регистр
        ПО ВТ.Номенклатура = Регистр.Номенклатура
        И ВТ.МаксПериод = Регистр.Период
ГРУППИРОВАТЬ ПО
    Регистр.Номенклатура,
    ВТ.МаксПериод
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Находим максимальный номер строки для выбранного регистратора
ВЫБРАТЬ
    Регистр.Номенклатура КАК Номенклатура,
    ВТ.Период КАК Период,
    ВТ.МаксРегистратор КАК Регистратор,
    МАКСИМУМ(Регистр.НомерСтроки) КАК МаксНомерСтроки
ПОМЕСТИТЬ ВТ_ИтоговыеКлючи
ИЗ
    ВТ_МаксРегистратор КАК ВТ
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Регистр
        ПО ВТ.Номенклатура = Регистр.Номенклатура
        И ВТ.Период = Регистр.Период
        И ВТ.МаксРегистратор = Регистр.Регистратор
ГРУППИРОВАТЬ ПО
    Регистр.Номенклатура,
    ВТ.Период,
    ВТ.МаксРегистратор
;

////////////////////////////////////////////////////////////////////////////////
// Шаг 4: Получаем финальные данные по уникальному ключу
ВЫБРАТЬ
    Ключи.Номенклатура КАК Номенклатура,
    Ключи.Период КАК Период,
    Регистр.Цена КАК Цена
ИЗ
    ВТ_ИтоговыеКлючи КАК Ключи
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Регистр
        ПО Ключи.Номенклатура = Регистр.Номенклатура
        И Ключи.Период = Регистр.Период
        И Ключи.Регистратор = Регистр.Регистратор
        И Ключи.МаксНомерСтроки = Регистр.НомерСтроки

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

Метод 2. Интервальный метод (Метод интервалов)

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

Суть данного подхода заключается в преобразовании точечных событий регистрации изменений в непрерывные временные интервалы. Для каждой записи регистра мы рассчитываем две границы:

Когда у нас есть готовая таблица интервалов, получение среза последних на любую произвольную дату (или список разных дат из документов) сводится к простейшему соединению по условию МЕЖДУ:


ВЫБРАТЬ
    Документы.Номенклатура КАК Номенклатура,
    Документы.Дата КАК ДатаАнализа,
    ВТ_Интервалы.Цена КАК Цена
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК Документы
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИнтервалыЦен КАК ВТ_Интервалы
        ПО Документы.Номенклатура = ВТ_Интервалы.Номенклатура
        И Документы.Дата МЕЖДУ ВТ_Интервалы.ПериодНачала И ВТ_Интервалы.ПериодОкончания

Преимущество метода: СУБД очень эффективно обрабатывает соединение по условию диапазона дат с использованием индексов. Этот способ незаменим, когда в рамках одного запроса вам нужно получить состояние регистра на множество разных дат для различных документов.

Метод 3. Связи наборов данных в СКД (Системе компоновки данных)

Если ваша задача — построить отчет для пользователя, то ручное написание сложных временных таблиц в одном общем запросе может оказаться неоптимальным решением. В Системе компоновки данных (СКД) заложена мощная альтернативная технология — Связи наборов данных.

Давайте разберем по шагам, как это настроить:

  1. Создайте НаборДанных1 (запрос), который получает список необходимых элементов справочника (например, список номенклатуры).
  2. Создайте НаборДанных2 (запрос), который выбирает данные из виртуальной таблицы РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, Номенклатура = &Номенклатура). Обратите внимание, что мы используем параметры.
  3. Перейдите на вкладку Связи наборов данных. Добавьте новую связь, где источником будет НаборДанных1, а приемником — НаборДанных2.
  4. Укажите выражение связи по ключевому полю (например, Номенклатура).
  5. В колонке Параметры свяжите параметр Номенклатура из второго набора с полем Номенклатура первого набора. Если срез последних нужен на определенную дату, передайте дату строки первого набора в параметр Период.

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

Влияние на производительность СУБД (Особенности PostgreSQL)

При разработке в клиент-серверном варианте (особенно на СУБД PostgreSQL, которая активно внедряется в рамках проектов импортозамещения) крайне важно избегать типовых архитектурных ошибок проектирования запросов.

Главная опасность при получении последних записей — это прямое соединение физических таблиц документов или справочников с виртуальной таблицей СрезПоследних без жестких параметров внутри самой виртуальной таблицы. Проанализируем, почему так происходит:

Для предотвращения этой проблемы всегда следуйте профессиональным стандартам разработки 1С:

  1. Сначала отбирайте анализируемый массив данных (например, только те товары, которые есть в документе) во временную таблицу.
  2. Обязательно индексируйте временные таблицы по полям связи. Для этого в конструкторе запросов на вкладке «Индексы» или в тексте запроса используйте инструкцию ИНДЕКСИРОВАТЬ ПО. Например: ПОМЕСТИТЬ ВТ_Товары ИНДЕКСИРОВАТЬ ПО Номенклатура
  3. Используйте полученную временную таблицу для связи только с физической таблицей регистра сведений при поиске максимальных дат. Физические таблицы всегда снабжены стандартными индексами СУБД, что гарантирует мгновенный поиск по связям.

Соблюдение этих несложных правил позволит вашим запросам выполняться за доли секунды даже на высоконагруженных многопользовательских базах данных.

← На главную