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

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

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

Разберем детально, почему это происходит, как платформа 1С выполняет подобные запросы, и рассмотрим эффективные варианты оптимизации.

Особенности работы динамического списка и виртуальных таблиц

Выясним причину падения производительности. По умолчанию платформа считывает данные динамического списка порционно (постранично). Если в свойствах списка корректно указана Основная таблица (например, Справочник.Номенклатура), платформа сначала по индексам основной таблицы получает порцию ссылок (обычно 20–50 видимых на экране строк), а затем для них подтягивает соединенные данные.

Однако при соединении с виртуальной таблицей РегистрСведений.ЦеныНоменклатуры.СрезПоследних возникают следующие сложности:

  1. Расчет максимума периода на уровне СУБД: Если в регистре не включено хранение итогов среза последних или в виртуальную таблицу передается дата среза, СУБД вынуждена выполнять тяжелый подзапрос с группировкой по максимальной дате по всему объему регистра.
  2. Потеря порционности: Если пользователь наложит отбор или сортировку по полю из правой таблицы (например, по цене), СУБД соединит всю таблицу номенклатуры со всем срезом регистра и выполнит сортировку всего массива строк, что полностью нивелирует порционное считывание.
  3. Дублирование строк: Если в регистре сведений несколько измерений (например, Номенклатура, ВидЦены, Характеристика), а в параметрах виртуальной таблицы нет жесткого отбора по ним, запрос вернет дубли строк номенклатуры или вызовет системную ошибку "Дублирование ключевых полей в поле ссылка".

Вариант 1. Оптимизация текста запроса и параметров виртуальной таблицы

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

Посмотрим на пример правильного формирования текста запроса с использованием расширения языка запросов для динамических списков (фигурные скобки {}):


ВЫБРАТЬ
    СправочникНоменклатура.Ссылка КАК Ссылка,
    СправочникНоменклатура.Артикул КАК Артикул,
    СправочникНоменклатура.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
    ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
ИЗ
    Справочник.Номенклатура КАК СправочникНоменклатура
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, {(ВидЦены = &ВидЦены), (Номенклатура.ТипНоменклатуры = &ТипНоменклатуры)}) КАК ЦеныНоменклатурыСрезПоследних
        ПО СправочникНоменклатура.Ссылка = ЦеныНоменклатурыСрезПоследних.Номенклатура
{ГДЕ
    (СправочникНоменклатура.ТипНоменклатуры = &ТипНоменклатуры)}

Разберем по шагам ключевые моменты этой конструкции:

  1. Передача условий в параметры виртуальной таблицы: Ограничение {(ВидЦены = &ВидЦены)} обязательно передается в первый параметр среза последних, чтобы исключить размножение строк.
  2. Фильтрация виртуальной таблицы: Условие {(Номенклатура.ТипНоменклатуры = &ТипНоменклатуры)} в параметрах среза позволяет СУБД отсечь ненужные записи регистра еще до выполнения операции соединения.
  3. Фильтрация основной таблицы: Блок {ГДЕ (СправочникНоменклатура.ТипНоменклатуры = &ТипНоменклатуры)} накладывает условие на сам справочник при установке соответствующего отбора в форме.

Вариант 2. Включение механизма итогов среза последних

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

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

Важные требования при использовании итогов:

  1. В виртуальную таблицу не должна передаваться дата среза (срез должен браться строго на текущий момент).
  2. Измерение Номенклатура должно стоять первым в списке измерений регистра (или иметь признак Ведущее), чтобы поиск по физической таблице итогов шел по кластерному индексу.
  3. Реквизит ТипНоменклатуры в самом справочнике должен иметь свойство «Индексировать».
  4. Стандарты разработки не рекомендуют обращаться через точку к полям ссылок в условиях среза последних при включенных итогах, так как это может блокировать использование готового индекса. Оптимально накладывать отбор по реквизиту справочника в секции ГДЕ, а срез последних соединять напрямую по ссылке номенклатуры.

Вариант 3. Заполнение цен через событие ПриПолученииДанныхНаСервере

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

Разберем по шагам реализацию данного шаблона:

  1. В динамическом списке оставляем простой запрос только к таблице Справочник.Номенклатура.
  2. В таблицу формы добавляем колонку Цена (без привязки к пути к данным списка).
  3. В модуле формы определяем обработчик события таблицы формы ПриПолученииДанныхНаСервере.

Посмотрим на пример программного кода обработчика:


&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(Элемент, ОформленияСтрок)
    
    МассивНоменклатуры = Новый Массив;
    СоответствиеСтрок = Новый Соответствие;
    
    Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
        МассивНоменклатуры.Добавить(СтрокаОформления.ДанныеСтроки.Ссылка);
        СоответствиеСтрок.Вставить(СтрокаОформления.ДанныеСтроки.Ссылка, СтрокаОформления);
    КонецЦикла;
    
    Если МассивНоменклатуры.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Цены.Номенклатура КАК Номенклатура,
        |	Цены.Цена КАК Цена
        |ИЗ
        |	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
        |			,
        |			Номенклатура В (&МассивНоменклатуры)
        |				И ВидЦены = &ВидЦены) КАК Цены";
        
    Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    Запрос.УстановитьПараметр("ВидЦены", ТекущийВидЦен);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        СтрокаОформления = СоответствиеСтрок[Выборка.Номенклатура];
        Если СтрокаОформления <> Неопределено Тогда
            СтрокаОформления.Ячейки.Цена.УстановитьТекст(Формат(Выборка.Цена, "ЧДЦ=2"));
        КонецЕсли;
    КонецЦикла;
    
КонецПроцедуры

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

Сводные рекомендации

  1. Всегда указывайте Основную таблицу в свойствах динамического списка.
  2. Обязательно передавайте отборы по всем измерениям регистра сведений (кроме номенклатуры) в параметры виртуальной таблицы СрезПоследних.
  3. Включайте флаг «Разрешить итоги: Срез последних» в метаданных регистра сведений и индексируйте реквизиты, по которым осуществляются пользовательские отборы.
  4. При критических требованиях к скорости отклика интерфейса на больших базах используйте событие ПриПолученииДанныхНаСервере или вспомогательные непериодические регистры актуальных цен.
← На главную