При разработке интерфейсов на управляемых формах часто возникает задача отобразить в динамическом списке справочника актуальные данные из периодических регистров сведений, например, действующие цены номенклатуры, текущие остатки или статусы. Простое добавление ЛЕВОЕ СОЕДИНЕНИЕ с виртуальной таблицей СрезПоследних может привести к существенному замедлению открытия и прокрутки формы списка при росте объема данных.
Разберем детально, почему это происходит, как платформа 1С выполняет подобные запросы, и рассмотрим эффективные варианты оптимизации.
Выясним причину падения производительности. По умолчанию платформа считывает данные динамического списка порционно (постранично). Если в свойствах списка корректно указана Основная таблица (например, Справочник.Номенклатура), платформа сначала по индексам основной таблицы получает порцию ссылок (обычно 20–50 видимых на экране строк), а затем для них подтягивает соединенные данные.
Однако при соединении с виртуальной таблицей РегистрСведений.ЦеныНоменклатуры.СрезПоследних возникают следующие сложности:
Номенклатура, ВидЦены, Характеристика), а в параметрах виртуальной таблицы нет жесткого отбора по ним, запрос вернет дубли строк номенклатуры или вызовет системную ошибку "Дублирование ключевых полей в поле ссылка".Проанализируем базовый запрос, в котором требуется получить список номенклатуры с ценами и возможностью фильтрации по типу номенклатуры. Нельзя просто накладывать условие через ГДЕ на левую таблицу, забывая про виртуальную таблицу регистра.
Посмотрим на пример правильного формирования текста запроса с использованием расширения языка запросов для динамических списков (фигурные скобки {}):
ВЫБРАТЬ
СправочникНоменклатура.Ссылка КАК Ссылка,
СправочникНоменклатура.Артикул КАК Артикул,
СправочникНоменклатура.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
ЦеныНоменклатурыСрезПоследних.Цена КАК Цена
ИЗ
Справочник.Номенклатура КАК СправочникНоменклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(, {(ВидЦены = &ВидЦены), (Номенклатура.ТипНоменклатуры = &ТипНоменклатуры)}) КАК ЦеныНоменклатурыСрезПоследних
ПО СправочникНоменклатура.Ссылка = ЦеныНоменклатурыСрезПоследних.Номенклатура
{ГДЕ
(СправочникНоменклатура.ТипНоменклатуры = &ТипНоменклатуры)}
Разберем по шагам ключевые моменты этой конструкции:
{(ВидЦены = &ВидЦены)} обязательно передается в первый параметр среза последних, чтобы исключить размножение строк.{(Номенклатура.ТипНоменклатуры = &ТипНоменклатуры)} в параметрах среза позволяет СУБД отсечь ненужные записи регистра еще до выполнения операции соединения.{ГДЕ (СправочникНоменклатура.ТипНоменклатуры = &ТипНоменклатуры)} накладывает условие на сам справочник при установке соответствующего отбора в форме.Рассмотрим подробнее штатную возможность платформы ускорить получение среза последних. Для периодических регистров сведений в дереве метаданных доступно свойство «Разрешить итоги: Срез последних».
Когда данный флажок установлен, платформа создает в базе данных отдельную физическую таблицу итогов, в которой всегда хранятся актуальные последние записи. Это превращает получение среза последних из тяжелой агрегатной функции с вычислением MAX(Период) в простое чтение физической таблицы по индексу.
Важные требования при использовании итогов:
Номенклатура должно стоять первым в списке измерений регистра (или иметь признак Ведущее), чтобы поиск по физической таблице итогов шел по кластерному индексу.ТипНоменклатуры в самом справочнике должен иметь свойство «Индексировать».ГДЕ, а срез последних соединять напрямую по ссылке номенклатуры.Если справочник содержит сотни тысяч позиций, а в регистре цен хранятся миллионы записей, даже оптимизированное соединение может создавать нагрузку. В таких случаях наиболее производительным решением является исключение соединения из текста динамического списка и подтягивание цен только для видимых на экране строк.
Разберем по шагам реализацию данного шаблона:
Справочник.Номенклатура.Цена (без привязки к пути к данным списка).ПриПолученииДанныхНаСервере.Посмотрим на пример программного кода обработчика:
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(Элемент, ОформленияСтрок)
МассивНоменклатуры = Новый Массив;
СоответствиеСтрок = Новый Соответствие;
Для Каждого СтрокаОформления Из ОформленияСтрок Цикл
МассивНоменклатуры.Добавить(СтрокаОформления.ДанныеСтроки.Ссылка);
СоответствиеСтрок.Вставить(СтрокаОформления.ДанныеСтроки.Ссылка, СтрокаОформления);
КонецЦикла;
Если МассивНоменклатуры.Количество() = 0 Тогда
Возврат;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Цены.Номенклатура КАК Номенклатура,
| Цены.Цена КАК Цена
|ИЗ
| РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
| ,
| Номенклатура В (&МассивНоменклатуры)
| И ВидЦены = &ВидЦены) КАК Цены";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
Запрос.УстановитьПараметр("ВидЦены", ТекущийВидЦен);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
СтрокаОформления = СоответствиеСтрок[Выборка.Номенклатура];
Если СтрокаОформления <> Неопределено Тогда
СтрокаОформления.Ячейки.Цена.УстановитьТекст(Формат(Выборка.Цена, "ЧДЦ=2"));
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Такой подход гарантирует, что запрос к регистру сведений всегда выбирает данные ровно для 20–40 строк, отображаемых на мониторе пользователя, полностью исключая сканирование таблиц СУБД целиком.
СрезПоследних.ПриПолученииДанныхНаСервере или вспомогательные непериодические регистры актуальных цен.