При разработке отчетов или интерактивных форм часто возникает задача: определить экстремумы цен (максимум, минимум или срез изменений) по конкретной номенклатуре за выбранный временной интервал. На первый взгляд задача кажется тривиальной, однако при прямом обращении к физической таблице периодического регистра сведений разработчики сталкиваются как с методическими ошибками расчета, так и с проблемами производительности СУБД. Разберем по шагам, как правильно составить такой запрос, избежать потери данных и оптимизировать скорость работы программы.
Рассмотрим распространенную логическую ошибку, когда отбор данных строится исключительно по попаданию даты записи регистра в заданный диапазон дат. Проанализируем типичный пример такого запроса:
ВЫБРАТЬ
МАКСИМУМ(ЦеныНоменклатурыКонтрагента.Цена) КАК МаксимальнаяЦена,
МИНИМУМ(ЦеныНоменклатурыКонтрагента.Цена) КАК МинимальнаяЦена
ИЗ
РегистрСведений.ЦеныНоменклатурыКонтрагента КАК ЦеныНоменклатурыКонтрагента
ГДЕ
ЦеныНоменклатурыКонтрагента.НоменклатураКонтрагента = &НоменклатураКонтрагента
И ЦеныНоменклатурыКонтрагента.Период МЕЖДУ &НачалоПериода И &КонецПериода
В чем заключается главная проблема этого подхода? Периодический регистр сведений хранит не непрерывный поток данных, а только факты изменения значений. Если цена на товар была установлена за день до даты &НачалоПериода и в течение всего выбранного интервала ни разу не менялась, данный запрос вернет NULL (пустой результат). Хотя фактически на протяжении всего интервала действовала ранее установленная цена.
Также обратим внимание на расчет агрегата СРЕДНЕЕ(Цена): если рассчитать его напрямую по записям регистра, мы получим среднее арифметическое по фактам смены прайс-листа, а не средневзвешенную по времени цену. Для реальной средней цены необходимо учитывать длительность действия каждого значения.
Чтобы получить абсолютно точный максимум и минимум цен, действовавших в заданном диапазоне, необходимо объединить два набора данных:
СрезПоследних на дату &НачалоПериода.Разберем готовый оптимизированный запрос, объединяющий эти условия:
ВЫБРАТЬ
СрезНаНачало.Цена КАК Цена
ПОМЕСТИТЬ ВТ_ВсеЦеныПериода
ИЗ
РегистрСведений.ЦеныНоменклатурыКонтрагента.СрезПоследних(
&НачалоПериода,
НоменклатураКонтрагента В (&МассивНоменклатурыКонтрагентов)) КАК СрезНаНачало
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
РегистрЦен.Цена
ИЗ
РегистрСведений.ЦеныНоменклатурыКонтрагента КАК РегистрЦен
ГДЕ
РегистрЦен.НоменклатураКонтрагента В (&МассивНоменклатурыКонтрагентов)
И РегистрЦен.Период > &НачалоПериода
И РегистрЦен.Период <= &КонецПериода
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
МИНИМУМ(ВТ_ВсеЦеныПериода.Цена) КАК МинимальнаяЦена,
МАКСИМУМ(ВТ_ВсеЦеныПериода.Цена) КАК МаксимальнаяЦена
ИЗ
ВТ_ВсеЦеныПериода КАК ВТ_ВсеЦеныПериода
Посмотрим на преимущества данной схемы: если в периоде не было ни одного нового документа установки цен, запрос корректно возьмет значение из виртуальной таблицы среза последних. Если же цены менялись несколько раз, объединение соберет полную историю и функция МАКСИМУМ вернет точный результат.
Выясним причину, почему исходные запросы могут выполняться медленно на больших объемах данных в СУБД:
ГДЕ Регистр.НоменклатураКонтрагента.Номенклатура = &Номенклатура заставляет платформу выполнять неявное левое соединение со справочником. В ряде случаев это ломает использование кластерного или вторичного индекса таблицы регистра и приводит к сканированию всей таблицы (Table/Index Scan).Справочник.НоменклатураКонтрагентов программно или через временную таблицу, а затем передать готовый массив ссылок через условие В (&МассивНоменклатурыКонтрагентов) в параметры виртуальной таблицы и блок ГДЕ.Измерение1 + Период + .... Накладывая фильтр строго на первое измерение и интервал дат, мы обеспечиваем максимальную скорость поиска (Index Seek).Если получение экстремумов цен выполняется в управляемой форме в событии ПриАктивизацииСтроки динамического списка, выполнение серверного запроса при каждом движении курсора создаст существенные задержки интерфейса. Проанализируем рекомендации по ускорению:
ПодключитьОбработчикОжидания с таймаутом в 0.2–0.3 секунды, чтобы запрос выполнялся только после того, как пользователь зафиксировал курсор на нужной позиции списка.Соответствие по ключу ссылки. Если пользователь возвращается к уже просмотренной строке, данные отображаются мгновенно без повторного обращения к базе данных.