Запросы к виртуальной таблице СрезПоследних периодического регистра сведений — одна из самых частых операций в разработке на платформе 1С:Предприятие. Однако попытка наложить фильтр по ресурсу в параметрах этой виртуальной таблицы часто приводит разработчиков к двум серьезным проблемам: критическому падению производительности СУБД (вплоть до зависания системы) и получению неверных, логически искаженных данных. Ситуация усложняется, если конфигурация находится на поддержке и мы не можем напрямую изменить структуру регистра (например, сделать ресурс измерением).
В этой статье мы подробно разберем, почему возникает эта проблема «под капотом» платформы 1С и СУБД, а также рассмотрим несколько эффективных практических способов её решения.
---
Прежде чем перейти к оптимизации быстродействия, давайте разберем логику работы платформы 1С. Многие начинающие разработчики путают поведение отборов, указанных в параметрах виртуальной таблицы, и отборов, указанных в секции ГДЕ.
Проанализируем классический пример. Предположим, у нас есть периодический регистр сведений «ИсторияСкидок». У него есть измерение Товар и ресурс Скидка. В регистре содержатся следующие записи для товара «Молоко»:
Наша задача — получить список товаров, у которых актуальная (текущая на сегодня) скидка равна 50%. Наш товар «Молоко» не должен попасть в этот список, так как его актуальная скидка сейчас составляет 20%.
Посмотрим, что произойдет при разных вариантах написания запроса.
Разработчик пишет следующий запрос:
ВЫБРАТЬ
Скидки.Товар,
Скидки.Скидка
ИЗ
РегистрСведений.ИсторияСкидок.СрезПоследних(, Скидка = 50) КАК Скидки
Как это обрабатывает 1С: Платформа транслирует этот запрос в SQL таким образом, что фильтр по скидке накладывается ДО расчета среза последних. СУБД сначала берет физическую таблицу регистра, жестко отсекает все строки, где скидка не равна 50% (строка от 10.01.2026 отбрасывается!), а затем среди оставшихся строк ищет самую свежую дату для каждого товара. В итоге запрос найдет запись от 05.01.2026 и вернет нам товар «Молоко» со скидкой 50%. Это грубая логическая ошибка! Мы получили неактуальные исторические данные.
Правильный с точки зрения логики запрос должен выглядеть так:
ВЫБРАТЬ
Скидки.Товар,
Скидки.Скидка
ИЗ
РегистрСведений.ИсторияСкидок.СрезПоследних() КАК Скидки
ГДЕ
Скидки.Скидка = 50
Как это обрабатывает 1С: Сначала система честно получает актуальный срез последних по всем товарам без исключения (для «Молока» будет выбрана единственная актуальная запись от 10.01.2026 со скидкой 20%). И только после этого на полученный результат накладывается фильтр ГДЕ Скидка = 50. Запись по «Молоку» отсекается, и мы получаем корректный результат. Но здесь мы сталкиваемся со второй проблемой — производительностью.
---
Перенос отбора в секцию ГДЕ решает проблему логики, но на больших базах данных (особенно под управлением PostgreSQL) такой запрос начинает выполняться недопустимо долго. Выделим три основные причины:
Table Scan или Clustered Index Scan), считывая миллионы строк с диска в оперативную память.СрезПоследних не существует. Платформа генерирует тяжелый подзапрос с соединением физической таблицы регистра самой с собой по максимальной дате в разрезе измерений. Выполнение полного сканирования поверх такого тяжелого соединения парализует работу СУБД.Seq Scan) с вложенными циклами (Nested Loop).Рассмотрим детально, как мы можем обойти эти ограничения без изменения метаданных конфигурации.
---
Чтобы кардинально повысить скорость запроса и помочь планировщику СУБД построить оптимальный план, нам нужно локализовать выборку. Мы разделим задачу на два простых шага: сначала быстро найдем только те измерения (например, товары), по которым вообще были движения с нужным нам значением ресурса, а затем получим срез последних только по этим измерениям.
Рассмотрим пример реализации этого подхода:
// Шаг 1: Отбираем только нужные измерения во временную таблицу по физической таблице регистра
ВЫБРАТЬ РАЗЛИЧНЫЕ
РегистрСкидок.Товар КАК Товар
ПОМЕСТИТЬ ВТ_ОтфильтрованныеТовары
ИЗ
РегистрСведений.ИсторияСкидок КАК РегистрСкидок
ГДЕ
РегистрСкидок.Скидка = &ЗначениеСкидки
ИНДЕКСИРОВАТЬ ПО
Товар
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем срез последних, передавая отобранные измерения в параметры виртуальной таблицы
ВЫБРАТЬ
СрезПоследних.Товар,
СрезПоследних.Скидка,
СрезПоследних.ДругойРесурс
ИЗ
РегистрСведений.ИсторияСкидок.СрезПоследних(, Товар В (ВЫБРАТЬ ВТ.Товар ИЗ ВТ_ОтфильтрованныеТовары КАК ВТ)) КАК СрезПоследних
ГДЕ
СрезПоследних.Скидка = &ЗначениеСкидки
Почему это работает быстро:
РегистрСведений.ИсторияСкидок. Даже при отсутствии индекса по ресурсу, простое сканирование плоской таблицы происходит в разы быстрее, чем расчет виртуального среза последних для всей базы.СрезПоследних. СУБД мгновенно рассчитывает срез последних по индексу только для этой узкой выборки товаров, полностью игнорируя остальную многомиллионную таблицу.ГДЕ мы оставляем финальную проверку скидки, чтобы исключить товары, у которых скидка уже изменилась на другую (решаем логическую проблему).---
Иногда стандартный механизм трансляции виртуальной таблицы 1С в SQL-код оказывается избыточным. Мы можем написать запрос к реальной физической таблице регистра сведений самостоятельно. Это позволит СУБД построить максимально простой и эффективный план выполнения группировок.
Разберем по шагам, как построить такой запрос:
// Шаг 1: Находим максимальные даты актуальности для каждой комбинации измерений
ВЫБРАТЬ
Регистр.Товар КАК Товар,
МАКСИМУМ(Регистр.Период) КАК Период
ПОМЕСТИТЬ ВТ_МаксимальныеДаты
ИЗ
РегистрСведений.ИсторияСкидок КАК Регистр
ГРУППИРОВАТЬ ПО
Регистр.Товар
ИНДЕКСИРОВАТЬ ПО
Товар,
Период
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Соединяем полученные даты с физической таблицей для получения актуального ресурса
ВЫБРАТЬ
Регистр.Товар,
Регистр.Скидка,
Регистр.ДругойРесурс
ИЗ
ВТ_МаксимальныеДаты КАК МаксДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ИсторияСкидок КАК Регистр
ПО МаксДаты.Товар = Регистр.Товар
И МаксДаты.Период = Регистр.Период
ГДЕ
Регистр.Скидка = &ЗначениеСкидки
Преимущества ручного среза: Такой запрос работает стабильно и предсказуемо на любых СУБД. Оптимизатору SQL гораздо проще обработать конструкцию МАКСИМУМ(Период) с последующим внутренним соединением по индексированным полям временной таблицы, нежели разбирать генерируемый платформой SQL-код виртуальной таблицы.
---
Если запросы к регистру сведений выполняются очень часто (например, в высоконагруженном динамическом списке, подборе товаров или API-интеграциях), то постоянный расчет среза последних «на лету» в любом виде будет создавать избыточную нагрузку на дисковую подсистему и процессор сервера СУБД.
В таком случае правильным решением будет перенос логики расчета на момент записи данных. Разберем этот алгоритм:
Скидка (или любой другой ресурс, по которому нужен быстрый отбор) мы делаем измерением, либо ставим у реквизита флаг «Индексировать».ПриЗаписи для исходного регистра сведений «ИсторияСкидок».СрезПоследних тяжелого регистра. Вместо этого мы делаем простейший и мгновенный выбор из плоской таблицы нового регистра сведений:
ВЫБРАТЬ
АктуальныеСкидки.Товар,
АктуальныеСкидки.Скидка
ИЗ
РегистрСведений.АктуальныеСкидкиТоваров КАК АктуальныеСкидки
ГДЕ
АктуальныеСкидки.Скидка = &ЗначениеСкидки
Этот подход требует времени на реализацию, но гарантирует стабильную работу высоконагруженной системы, так как тяжелый расчет выполняется один раз в момент записи документа, а чтение происходит за миллисекунды.
---
Если конфигурация полностью закрыта для изменений, а скорость выполнения запроса критична прямо сейчас, системный администратор или DBA (администратор баз данных) может прибегнуть к созданию индекса непосредственно в консоли СУБД.
Выясним причину эффективности этого метода и правила его применения:
_InfoRg12345) и имя колонки ресурса (например, _Fld12347). В консоли MS SQL Server или PostgreSQL запускается скрипт создания индекса по данному полю. После этого СУБД перестанет делать full scan таблицы при запросах с фильтром по ресурсу.Ресурс1 = Истина или определенным статусом), можно создать частичный индекс. В PostgreSQL это выглядит следующим образом:
CREATE INDEX idx_inforg12345_filtered
ON _InfoRg12345 (_Fld12347)
WHERE _Fld12347 = 1;
Критически важные предупреждения при использовании административного хака:
---
Подводя итоги нашего анализа работы со срезом последних при отборе по ресурсу, выделим ключевые правила:
СрезПоследних — это приводит к логическим ошибкам в результатах запроса.ГДЕ.