Как оптимизировать запрос «Срез последних» с отбором по ресурсу в 1С и избежать логических ошибок

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

Запросы к виртуальной таблице СрезПоследних периодического регистра сведений — одна из самых частых операций в разработке на платформе 1С:Предприятие. Однако попытка наложить фильтр по ресурсу в параметрах этой виртуальной таблицы часто приводит разработчиков к двум серьезным проблемам: критическому падению производительности СУБД (вплоть до зависания системы) и получению неверных, логически искаженных данных. Ситуация усложняется, если конфигурация находится на поддержке и мы не можем напрямую изменить структуру регистра (например, сделать ресурс измерением).

В этой статье мы подробно разберем, почему возникает эта проблема «под капотом» платформы 1С и СУБД, а также рассмотрим несколько эффективных практических способов её решения.

---

В чем заключается логическая ошибка отбора по ресурсу?

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

Проанализируем классический пример. Предположим, у нас есть периодический регистр сведений «ИсторияСкидок». У него есть измерение Товар и ресурс Скидка. В регистре содержатся следующие записи для товара «Молоко»:

  1. 01.01.2026 — Скидка = 10%
  2. 05.01.2026 — Скидка = 50%
  3. 10.01.2026 — Скидка = 20% (это текущее, самое последнее и актуальное состояние цены товара)

Наша задача — получить список товаров, у которых актуальная (текущая на сегодня) скидка равна 50%. Наш товар «Молоко» не должен попасть в этот список, так как его актуальная скидка сейчас составляет 20%.

Посмотрим, что произойдет при разных вариантах написания запроса.

Вариант А. Отбор в параметрах виртуальной таблицы (Логическая ошибка)

Разработчик пишет следующий запрос:


ВЫБРАТЬ
    Скидки.Товар,
    Скидки.Скидка
ИЗ
    РегистрСведений.ИсторияСкидок.СрезПоследних(, Скидка = 50) КАК Скидки

Как это обрабатывает 1С: Платформа транслирует этот запрос в SQL таким образом, что фильтр по скидке накладывается ДО расчета среза последних. СУБД сначала берет физическую таблицу регистра, жестко отсекает все строки, где скидка не равна 50% (строка от 10.01.2026 отбрасывается!), а затем среди оставшихся строк ищет самую свежую дату для каждого товара. В итоге запрос найдет запись от 05.01.2026 и вернет нам товар «Молоко» со скидкой 50%. Это грубая логическая ошибка! Мы получили неактуальные исторические данные.

Вариант Б. Отбор в секции ГДЕ (Логически верно)

Правильный с точки зрения логики запрос должен выглядеть так:


ВЫБРАТЬ
    Скидки.Товар,
    Скидки.Скидка
ИЗ
    РегистрСведений.ИсторияСкидок.СрезПоследних() КАК Скидки
ГДЕ
    Скидки.Скидка = 50

Как это обрабатывает 1С: Сначала система честно получает актуальный срез последних по всем товарам без исключения (для «Молока» будет выбрана единственная актуальная запись от 10.01.2026 со скидкой 20%). И только после этого на полученный результат накладывается фильтр ГДЕ Скидка = 50. Запись по «Молоку» отсекается, и мы получаем корректный результат. Но здесь мы сталкиваемся со второй проблемой — производительностью.

---

Почему запрос с секцией ГДЕ работает медленно?

Перенос отбора в секцию ГДЕ решает проблему логики, но на больших базах данных (особенно под управлением PostgreSQL) такой запрос начинает выполняться недопустимо долго. Выделим три основные причины:

  1. Отсутствие индексов на ресурсах: Платформа 1С автоматически создает индексы СУБД только по измерениям и периоду регистра. Поля ресурсов и реквизитов по умолчанию не индексируются. Любой поиск по неиндексированному полю заставляет СУБД выполнять полное сканирование таблицы (Table Scan или Clustered Index Scan), считывая миллионы строк с диска в оперативную память.
  2. Сложная структура виртуальной таблицы: На уровне SQL-сервера виртуальной таблицы СрезПоследних не существует. Платформа генерирует тяжелый подзапрос с соединением физической таблицы регистра самой с собой по максимальной дате в разрезе измерений. Выполнение полного сканирования поверх такого тяжелого соединения парализует работу СУБД.
  3. Особенности оптимизатора PostgreSQL: Оптимизатор PostgreSQL на таблицах объемом более 1 млн записей часто некорректно строит план выполнения для платформенного среза последних. Вместо использования индексов по измерениям он может выбрать неэффективное последовательное сканирование всей таблицы (Seq Scan) с вложенными циклами (Nested Loop).

Рассмотрим детально, как мы можем обойти эти ограничения без изменения метаданных конфигурации.

---

Вариант 1. Оптимизация через Временные Таблицы (Рекомендация вендора)

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

Рассмотрим пример реализации этого подхода:


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

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

Почему это работает быстро:

---

Вариант 2. «Ручной» срез последних (запрос к физической таблице)

Иногда стандартный механизм трансляции виртуальной таблицы 1С в SQL-код оказывается избыточным. Мы можем написать запрос к реальной физической таблице регистра сведений самостоятельно. Это позволит СУБД построить максимально простой и эффективный план выполнения группировок.

Разберем по шагам, как построить такой запрос:


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

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

Преимущества ручного среза: Такой запрос работает стабильно и предсказуемо на любых СУБД. Оптимизатору SQL гораздо проще обработать конструкцию МАКСИМУМ(Период) с последующим внутренним соединением по индексированным полям временной таблицы, нежели разбирать генерируемый платформой SQL-код виртуальной таблицы.

---

Вариант 3. Использование вспомогательного регистра (Архитектурное решение)

Если запросы к регистру сведений выполняются очень часто (например, в высоконагруженном динамическом списке, подборе товаров или API-интеграциях), то постоянный расчет среза последних «на лету» в любом виде будет создавать избыточную нагрузку на дисковую подсистему и процессор сервера СУБД.

В таком случае правильным решением будет перенос логики расчета на момент записи данных. Разберем этот алгоритм:

  1. Создаем новый вспомогательный регистр сведений (например, «АктуальныеСкидкиТоваров»). Этот регистр будет непериодическим. В нем поле Скидка (или любой другой ресурс, по которому нужен быстрый отбор) мы делаем измерением, либо ставим у реквизита флаг «Индексировать».
  2. Создаем подписку на событие ПриЗаписи для исходного регистра сведений «ИсторияСкидок».
  3. В обработчике подписки пишем код, который при любом изменении исходного регистра рассчитывает актуальное состояние для измененных товаров и перезаписывает эти данные в наш вспомогательный плоский регистр.
  4. В отчетах и тяжелых запросах мы полностью отказываемся от использования виртуальной таблицы СрезПоследних тяжелого регистра. Вместо этого мы делаем простейший и мгновенный выбор из плоской таблицы нового регистра сведений:

ВЫБРАТЬ
    АктуальныеСкидки.Товар,
    АктуальныеСкидки.Скидка
ИЗ
    РегистрСведений.АктуальныеСкидкиТоваров КАК АктуальныеСкидки
ГДЕ
    АктуальныеСкидки.Скидка = &ЗначениеСкидки

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

---

Вариант 4. Создание индекса средствами СУБД (Административный хак)

Если конфигурация полностью закрыта для изменений, а скорость выполнения запроса критична прямо сейчас, системный администратор или DBA (администратор баз данных) может прибегнуть к созданию индекса непосредственно в консоли СУБД.

Выясним причину эффективности этого метода и правила его применения:

Критически важные предупреждения при использовании административного хака:

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

---

Резюме и рекомендации

Подводя итоги нашего анализа работы со срезом последних при отборе по ресурсу, выделим ключевые правила:

← На главную