Django 数据库对 filter() 相同 QuerySet 的查询优化并分配给不同的变量

问题描述 投票:0回答:1

我有一个基础

queryset
,目标是使用基础
queryset
构建仪表板统计数据,但按每个变量的不同值进行过滤。

为了更好地理解我的简化代码:

class Transaction(models.Model):
    """ Invoice model.
     Represents a basic income/outcome transaction. """
    
    user = models.ForeignKey(CustomUser, related_name="transactions", on_delete=models.CASCADE)
    title = models.CharField(max_length=32, verbose_name="Title")
    category = models.ForeignKey(Category, related_name="transactions", on_delete=models.CASCADE, null=True, blank=True)
    operation = models.CharField(max_length=8, choices=OPERATION_TYPE, verbose_name="operation")
    value = models.DecimalField(max_digits=14, decimal_places=2, verbose_name="value")
    date_created = models.DateTimeField(auto_now_add=True, blank=True, null=True) 

class Category(MPTTModel):
    """ Category model. 
    Represents a category where money have been spent/earned."""

    name = models.CharField(max_length=54, unique=True)
    parent = TreeForeignKey("self", on_delete=models.CASCADE, null=True, blank=True, related_name='children')

有了我创建的上述模型

ClassBasedView(ListView)

class DashboardView(ListView):
    """ View implements dashboard functionality. """
    model = Transaction
    template_name = "invoices/dashboard.html"
    ordering = "-date_created"


    def get_queryset(self) -> QuerySet[Any]:
        queryset = super().get_queryset()
        queryset = queryset.filter(user=self.request.user).select_related("category__parent")
        return queryset

    def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
        data = super().get_context_data(**kwargs)

        # retrieving all transactons
        transactions_all = self.get_queryset()

        # retrieving incomes/expenses summary for current month
        incomes_this_month = transactions_all.filter(transaction_filter.transaction_date_filter(month="current"), operation="incomes")
        incomes_this_month_sum = incomes_this_month.aggregate(Sum("value")).get("value__sum")
        
        expenses_this_month = transactions_all.filter(transaction_filter.transaction_date_filter(month="current"), operation="expenses")                          
        expenses_this_month_sum = expenses_this_month.aggregate(Sum("value")).get("value__sum")
        
        # retrieving incomes/expenses summary for previous month
        transactions_prev_month = transactions_all.filter(transaction_filter.transaction_date_filter(month="previous"))
   
        incomes_previous_month = transactions_prev_month.filter(operation="incomes")
        incomes_previous_month_sum = incomes_previous_month.aggregate(Sum("value")).get("value__sum")

        expenses_previous_month = transactions_prev_month.filter(operation="expenses")
        expenses_previous_month_sum = expenses_previous_month.aggregate(Sum("value")).get("value__sum")

您可以在

transaction_date_filter
管理器中看到
filter()
。它只是一个用于日期过滤的
Q()
对象。

使用

select_related
我设法删除了重复项,但仍然存在“类似”查询,因为 Django ORM 请求各个查询通过
incomes_this_month
expenses_this_month
字段过滤
incomes_previous_month
expenses_previous_month
date=
operation=
字段。我知道
filter()
管理器“重置”缓存的对象,因此我试图找到一种更有效的方法来获取此类数据,而无需使用额外的查询访问数据库。 我已附上 DjDT 查询计数器屏幕截图。希望它能让问题更清楚。

感谢有关此主题的任何建议。

python django postgresql
1个回答
0
投票

为了将其简化为单个查询,您可以使用 SQL 的

GROUP_BY
,它可以让我们查询本月和上个月的所有收入和支出,然后将它们分成我们想要的组。我们可以在 Django 中通过在查询集上调用
.values()
来完成此操作。赋予
.values()
的参数是我们想要用来创建不同组的属性。对于您的情况,我们希望针对月份和交易操作的不同组合拥有不同的数据组。

transactions_all.values("month", "operation")

但在此查询生效之前,我们需要

.annotate()
查询集,以便每笔交易的属性“month”实际存在。我不知道你的
transaction_filter.transaction_date_filter
是如何工作的,所以我要写我自己的注释。您也许可以调整现有的日期过滤器以适应此目的。我将使用 ExtractMonth 从
date_created
字段中获取月份值。

from django.db.models.functions import ExtractMonth

transactions_all.annotate(month=ExtractMonth("date_created"))

现在带注释的查询集的一个问题是根本没有考虑交易的年份,所以我们会得到今年这个月进行的所有交易,但也会得到去年和今年的同月之前(每年三月而不是今年三月)。我们可以通过向查询集中添加过滤器来解决此问题,以确保我们只查询最近两个月的数据。

transactions_all.filter(date_created__range=[first_day_of_last_month, last_day_of_this_month])

添加到查询集中的最后一个内容是最终的

.annotate()
,它将总结每组中交易的值。此
.annotate()
替换了您在查询中使用的
.aggregate()
,但具有对交易值求和的相同目的。我们需要这样做是因为我们的结果有多个对象,因此我们使用
.annotate()
向查询集中的每个对象添加一个属性。使用
.aggregate()
会将查询集的结果压缩为单个对象,这会破坏我们所做的分组。

transactions_all.filter(
        date_created__range=[first_day_of_last_month, last_day_of_this_month]
    ).annotate(
        month=ExtractMonth("date_created")
    ).values(
        "month", "operation"
    ).annotate(
        value_sum=Sum("value")
    )

这将在单个 SQL 查询中获取我们需要的所有数据,并为我们提供如下所示的输出:

<QuerySet [
    {'operation': 'expenses', 'month': 2, 'value': Decimal('750')},
    {'operation': 'expenses', 'month': 3, 'value': Decimal('750')},
    {'operation': 'incomes', 'month': 2, 'value': Decimal('240')},
    {'operation': 'incomes', 'month': 3, 'value': Decimal('350')}
]>
© www.soinside.com 2019 - 2024. All rights reserved.