Как правильно и быстро получить максимальную цену номенклатуры из регистра сведений за период в 1С?

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

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

Анализ типовых ошибок: почему условие «Период МЕЖДУ» работает некорректно

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


ВЫБРАТЬ
    МАКСИМУМ(ЦеныНоменклатурыКонтрагента.Цена) КАК МаксимальнаяЦена,
    МИНИМУМ(ЦеныНоменклатурыКонтрагента.Цена) КАК МинимальнаяЦена
ИЗ
    РегистрСведений.ЦеныНоменклатурыКонтрагента КАК ЦеныНоменклатурыКонтрагента
ГДЕ
    ЦеныНоменклатурыКонтрагента.НоменклатураКонтрагента = &НоменклатураКонтрагента
    И ЦеныНоменклатурыКонтрагента.Период МЕЖДУ &НачалоПериода И &КонецПериода

В чем заключается главная проблема этого подхода? Периодический регистр сведений хранит не непрерывный поток данных, а только факты изменения значений. Если цена на товар была установлена за день до даты &НачалоПериода и в течение всего выбранного интервала ни разу не менялась, данный запрос вернет NULL (пустой результат). Хотя фактически на протяжении всего интервала действовала ранее установленная цена.

Также обратим внимание на расчет агрегата СРЕДНЕЕ(Цена): если рассчитать его напрямую по записям регистра, мы получим среднее арифметическое по фактам смены прайс-листа, а не средневзвешенную по времени цену. Для реальной средней цены необходимо учитывать длительность действия каждого значения.

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

Чтобы получить абсолютно точный максимум и минимум цен, действовавших в заданном диапазоне, необходимо объединить два набора данных:

  1. Цену, действовавшую на момент старта периода: для этого используется виртуальная таблица СрезПоследних на дату &НачалоПериода.
  2. Все изменения цен, произошедшие внутри периода: выборка из физической таблицы регистра за интервал строго больше даты начала и до даты окончания включительно.

Разберем готовый оптимизированный запрос, объединяющий эти условия:


ВЫБРАТЬ
    СрезНаНачало.Цена КАК Цена
ПОМЕСТИТЬ ВТ_ВсеЦеныПериода
ИЗ
    РегистрСведений.ЦеныНоменклатурыКонтрагента.СрезПоследних(
        &НачалоПериода,
        НоменклатураКонтрагента В (&МассивНоменклатурыКонтрагентов)) КАК СрезНаНачало

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    РегистрЦен.Цена
ИЗ
    РегистрСведений.ЦеныНоменклатурыКонтрагента КАК РегистрЦен
ГДЕ
    РегистрЦен.НоменклатураКонтрагента В (&МассивНоменклатурыКонтрагентов)
    И РегистрЦен.Период > &НачалоПериода
    И РегистрЦен.Период <= &КонецПериода
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    МИНИМУМ(ВТ_ВсеЦеныПериода.Цена) КАК МинимальнаяЦена,
    МАКСИМУМ(ВТ_ВсеЦеныПериода.Цена) КАК МаксимальнаяЦена
ИЗ
    ВТ_ВсеЦеныПериода КАК ВТ_ВсеЦеныПериода

Посмотрим на преимущества данной схемы: если в периоде не было ни одного нового документа установки цен, запрос корректно возьмет значение из виртуальной таблицы среза последних. Если же цены менялись несколько раз, объединение соберет полную историю и функция МАКСИМУМ вернет точный результат.

Оптимизация работы с индексами и разыменованием полей

Выясним причину, почему исходные запросы могут выполняться медленно на больших объемах данных в СУБД:

  1. Разыменование через точку в условиях отбора: конструкция вида ГДЕ Регистр.НоменклатураКонтрагента.Номенклатура = &Номенклатура заставляет платформу выполнять неявное левое соединение со справочником. В ряде случаев это ломает использование кластерного или вторичного индекса таблицы регистра и приводит к сканированию всей таблицы (Table/Index Scan).
  2. Использование параметров: гораздо быстрее предварительно получить список ссылок справочника Справочник.НоменклатураКонтрагентов программно или через временную таблицу, а затем передать готовый массив ссылок через условие В (&МассивНоменклатурыКонтрагентов) в параметры виртуальной таблицы и блок ГДЕ.
  3. Порядок измерений в регистре: платформа 1С автоматически строит индексы по ведущим измерениям регистра вида Измерение1 + Период + .... Накладывая фильтр строго на первое измерение и интервал дат, мы обеспечиваем максимальную скорость поиска (Index Seek).

Оптимизация контекста формы при активизации строки

Если получение экстремумов цен выполняется в управляемой форме в событии ПриАктивизацииСтроки динамического списка, выполнение серверного запроса при каждом движении курсора создаст существенные задержки интерфейса. Проанализируем рекомендации по ускорению:

  1. Подключение обработчика ожидания: не отправляйте запрос на сервер мгновенно при срабатывании события. Используйте метод ПодключитьОбработчикОжидания с таймаутом в 0.2–0.3 секунды, чтобы запрос выполнялся только после того, как пользователь зафиксировал курсор на нужной позиции списка.
  2. Кэширование на клиенте: сохраняйте полученные минимальные и максимальные значения в клиентскую коллекцию Соответствие по ключу ссылки. Если пользователь возвращается к уже просмотренной строке, данные отображаются мгновенно без повторного обращения к базе данных.
← На главную