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