Django Postgresql ORM оптимизация
У меня есть представление PostgreSQL под названием view_sales_dashboard - оно состоит из нескольких миллионов строк ежедневных данных о продажах.
В представлении Django я хочу представить таблицу, сгруппированную по продуктам, со столбцами в виде итогов base_daily_pnl за различные периоды времени - Ежедневно, Месяц до даты (MTD), Квартал до даты (QTD), Год до даты (YTD) и Начало до даты (ITD)
Для того чтобы попытаться ограничить количество SQL-запросов, я создаю 5 наборов запросов, чтобы затем сгенерировать таблицу. Чтобы повысить эффективность этого, я изучил журналы и ожидал увидеть 5 SQL-запросов. Однако журнал показывает 20 запросов (5 типов товаров * 4 агрегатные группировки + запрос ежедневной серии).
Смотрите ниже код Django, модель ORM и журналы.
Может ли кто-нибудь подсказать 1.) почему выполняется так много SQL-запросов 2.) как оптимизировать?
queryset_sales_all = SalesDashboard.objects.all()
queryset_daily_products = queryset_pnl_all.filter(position_date__range=[latest_pnl_date_str, latest_pnl_date_str]).values('product').annotate(base_daily_pnl=Sum('base_daily_pnl'),base_lmv=Sum('base_lmv'))
for daily in queryset_daily_product:
matching_mtd = queryset_pnl_all.filter(position_date__range=[start_mth_str,latest_pnl_date_str]).values('product').annotate(mtd_pnl=Sum('base_daily_pnl')).get(product=daily['product'])
matching_qtd = queryset_pnl_all.filter(position_date__range=[start_qtd_str, latest_pnl_date_str]).values('product').annotate(qtd_pnl=Sum('base_daily_pnl')).get(product=daily['product'])
matching_ytd = queryset_pnl_all.filter(position_date__range=[start_year_str, latest_pnl_date_str]).values('product').annotate(ytd_pnl=Sum('base_daily_pnl')).get(product=daily['product'])
matching_itd = queryset_pnl_all.filter(position_date__range=[start_itd_str, latest_pnl_date_str]).values('product').annotate(itd_pnl=Sum('base_daily_pnl')).get(product=daily['product'])
daily['mtd_pnl'] = matching_mtd['mtd_pnl']
daily['qtd_pnl'] = matching_qtd['qtd_pnl']
daily['ytd_pnl'] = matching_ytd['ytd_pnl']
daily['itd_pnl'] = matching_itd['itd_pnl']
pnl_product = SummaryPnlProductTable(queryset_daily_product)
Ниже представлена модель ORM:
class SalesDashboard(models.Model):
unqiue_id = models.IntegerField(primary_key=True)
sales_id = models.CharField(max_length=50)
base_daily_pnl = models.FloatField(default=0)
position_date = models.DateField()
book_id = models.IntegerField()
book = models.CharField(max_length=100, blank=True, null=True)
product = models.CharField(max_length=100, blank=True, null=True)
customer = models.CharField(max_length=100)
base_lmv = models.FloatField(default=0)
class Meta:
managed = False
db_table = 'view_sales_dashboard
Запись в журнал:
Вы можете легко "сгруппировать" запросы так, чтобы нам понадобилось только четыре для всех продуктов.
Сначала нам нужно сделать словари более доступными, мы можем сделать это с помощью:
lut = {item['products']: item for item in queryset_daily_products}
def update_lut(qs, start, target, lut):
for item in (
qs.filter(
position_date__range=[start, latest_pnl_date_str], product__in=lut
)
.values('product')
.annotate(result=Sum('base_daily_pnl'))
):
lut[item['product']][target] = item['result']
update_lut(queryset_pnl_all, start_mth_str, 'mtd_pnl', lut)
update_lut(queryset_pnl_all, start_qtd_str, 'qtd_pnl', lut)
update_lut(queryset_pnl_all, start_year_str, 'ytd_pnl', lut)
update_lut(queryset_pnl_all, start_itd_str, 'itd_pnl', lut)
На самом деле мы можем переписать это, чтобы сделать это с помощью одного запроса, используя filter=…
параметр [Django-doc], но это, скорее всего, не нужно.