При разработке аналитических отчетов в 1С (особенно на базе Системы компоновки данных) разработчики часто сталкиваются с ситуацией, когда запрос, объединяющий данные нескольких регистров, начинает выполняться крайне медленно. В этой статье разберем типичные ошибки проектирования таких запросов и пошагово оптимизируем сложный алгоритм получения остатков, оборотов продаж, цен и точек заказа.
Проанализируем исходный вариант запроса и выделим ключевые узкие места, которые приводят к существенному падению производительности СУБД:
Склад.Подразделение.ТипПодразделения и Продажи.Подразделение.Склад заставляют платформу генерировать неявные соединения (LEFT JOIN) с таблицами справочников, что усложняет план выполнения запроса.Номенклатура В (ВЫБРАТЬ ТСОИО.Номенклатура ИЗ ТСОИО) внутри параметров виртуальных таблиц (оборотов, срезов последних) часто мешает оптимизатору СУБД использовать индексы таблиц итогов.ИНДЕКСИРОВАТЬ ПО приводит к полному сканированию данных (Table Scan) в базе tempdb при каждом последующем соединении.Разберем, как избавиться от обращений через точку к реквизитам складов и подразделений. Вместо постоянного разыменования Склад.Подразделение.ТипПодразделения в каждой виртуальной таблице выберем список подходящих складов в отдельную проиндексированную временную таблицу.
ВЫБРАТЬ
Склады.Ссылка КАК Склад
ПОМЕСТИТЬ ВТ_Склады
ИЗ
Справочник.Склады КАК Склады
ГДЕ
Склады.Подразделение.ТипПодразделения = &ТипПодразделения
ИНДЕКСИРОВАТЬ ПО
Склад
;
Выясним причину объединения таблицы остатков с регистром сведений ЗначенияТочкиЗаказа. Это необходимо для того, чтобы в отчет попали позиции, по которым на текущий момент нет остатка, но установлена точка заказа (товар находится в матрице). Заменим тяжелую таблицу остатков и оборотов на таблицу Остатки и добавим обязательную индексацию.
ВЫБРАТЬ
ВЗ.Номенклатура КАК Номенклатура,
ВЗ.Склад КАК Склад
ПОМЕСТИТЬ ВТ_НоменклатураСклад
ИЗ
(ВЫБРАТЬ
ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
ТоварыНаСкладахОстатки.Склад КАК Склад
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
&КонецПериода,
Номенклатура В ИЕРАРХИИ (&ГруппаНоменклатуры)
И Склад В (ВЫБРАТЬ ВТ_Склады.Склад ИЗ ВТ_Склады)) КАК ТоварыНаСкладахОстатки
ОБЪЕДИНИТЬ
ВЫБРАТЬ
ТочкаЗаказа.Номенклатура,
ТочкаЗаказа.Склад
ИЗ
РегистрСведений.ЗначенияТочкиЗаказа.СрезПоследних(
&КонецПериода,
Номенклатура В ИЕРАРХИИ (&ГруппаНоменклатуры)
И Склад В (ВЫБРАТЬ ВТ_Склады.Склад ИЗ ВТ_Склады)) КАК ТочкаЗаказа) КАК ВЗ
ИНДЕКСИРОВАТЬ ПО
Номенклатура,
Склад
;
Для оптимизации последующих соединений с ценами и остатками основного склада выделим уникальный список товаров в отдельную временную таблицу:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ВТ_НоменклатураСклад.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
ВТ_НоменклатураСклад КАК ВТ_НоменклатураСклад
ИНДЕКСИРОВАТЬ ПО
Номенклатура
;
Теперь соберем все данные воедино. Обратите внимание на расчет остатков основного склада: они не должны размножаться по магазинам через неверную группировку, а должны соединяться строго по полю Номенклатура.
ВЫБРАТЬ
База.Номенклатура КАК Номенклатура,
База.Склад КАК Склад,
База.Номенклатура.СостояниеНоменклатуры КАК Состояние,
ЕСТЬNULL(ПродажиОбороты.КоличествоОборот, 0) КАК Продажи,
ЕСТЬNULL(ОстаткиМагазин.КоличествоОстаток, 0) КАК ОстатокМаг,
ЕСТЬNULL(ОстаткиОсновной.КоличествоОстаток, 0) КАК ОстатокОсн,
ЕСТЬNULL(ЦеныНоменклатуры.Цена, 0) КАК Цена,
ВЫБОР
КОГДА ЕСТЬNULL(ТочкаЗаказа.ЗначениеТочкиЗаказа, 0) > 0
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ КАК ВМатрице,
ЕСТЬNULL(ТочкаЗаказа.Период, ДАТАВРЕМЯ(1, 1, 1, 0, 0, 0)) КАК ДатаВводаВывода
ИЗ
ВТ_НоменклатураСклад КАК База
// Остатки по конкретным магазинам
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
&КонецПериода,
Номенклатура В (ВЫБРАТЬ ВТ_Товары.Номенклатура ИЗ ВТ_Товары)
И Склад В (ВЫБРАТЬ ВТ_Склады.Склад ИЗ ВТ_Склады)) КАК ОстаткиМагазин
ПО База.Номенклатура = ОстаткиМагазин.Номенклатура
И База.Склад = ОстаткиМагазин.Склад
// Остатки по центральному складу
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
&КонецПериода,
Номенклатура В (ВЫБРАТЬ ВТ_Товары.Номенклатура ИЗ ВТ_Товары)
И Склад = &ОсновнойСклад) КАК ОстаткиОсновной
ПО База.Номенклатура = ОстаткиОсновной.Номенклатура
// Продажи за период
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(
&НачалоПериода,
&КонецПериода,
Авто,
Номенклатура В (ВЫБРАТЬ ВТ_Товары.Номенклатура ИЗ ВТ_Товары)
И Подразделение В (ВЫБРАТЬ ВТ_Склады.Склад.Подразделение ИЗ ВТ_Склады)) КАК ПродажиОбороты
ПО База.Номенклатура = ПродажиОбороты.Номенклатура
// Цены номенклатуры
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
&КонецПериода,
ТипЦен = &ТипЦен
И Номенклатура В (ВЫБРАТЬ ВТ_Товары.Номенклатура ИЗ ВТ_Товары)) КАК ЦеныНоменклатуры
ПО База.Номенклатура = ЦеныНоменклатуры.Номенклатура
// Точка заказа
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияТочкиЗаказа.СрезПоследних(
&КонецПериода,
Номенклатура В (ВЫБРАТЬ ВТ_Товары.Номенклатура ИЗ ВТ_Товары)
И Склад В (ВЫБРАТЬ ВТ_Склады.Склад ИЗ ВТ_Склады)) КАК ТочкаЗаказа
ПО База.Номенклатура = ТочкаЗаказа.Номенклатура
И База.Склад = ТочкаЗаказа.Склад
Если отчет строится с использованием СКД, монолитный пакет запросов можно оптимизировать еще эффективнее:
ВЫБОР КОГДА ... ТОГДА 1 ИНАЧЕ 0 и функции ЕСТЬNULL перенесем в настройки вычисляемых полей или выражений ресурсов СКД. Это снизит объем вычислений на стороне СУБД.{ВЫБРАТЬ ...}, {ГДЕ ...}, что позволит СКД автоматически удалять неиспользуемые левые соединения из итогового SQL-текста.