优化Django SQL查询

时间:2019-01-17 05:29:41

标签: django django-orm

我正在使用 Django 2.x

我有两个模型

class AmountGiven(models.Model):
    contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
    amount = models.FloatField(help_text='Amount given to the contact')
    interest_rate = models.FloatField(blank=True, default=None, null=True)
    given_date = models.DateField(default=timezone.now)
    total_due = models.FloatField(blank=True, default=0.0, editable=False)

class AmountReturned(models.Model):
    amount_given = models.ForeignKey(AmountGiven, on_delete=models.CASCADE, blank=True)
    amount = models.FloatField()
    return_date = models.DateField(default=date.today)

用例

  • 可以有多个记录记录给联系人的金额
  • 对于给定的金额,可以有多个记录的退回金额

现在,我想为某个特定联系人获得total_due的金额。其中包括

total_payable = total_given + interest 
total_due = total_payable - total_returned

要计算total_dueinterest,我在 AmountGiven 模型中定义了几种属性方法。

@property
def interest_to_pay(self):
    if self.interest_rate:
        simple_interest_amount = ...
        return simple_interest_amount

    return 0

@property
def total_payable(self):
    return self.amount + self.interest_to_pay

@property
def amount_due(self):
    total_due = self.total_payable - self.total_returned

    self.total_due = total_due
    self.save()

    return total_due

@property
def total_returned(self):
    returned_amount = self.amountreturned_set.aggregate(total_returned=Sum('amount'))['total_returned']
    if not returned_amount:
        returned_amount = 0

    return returned_amount

联系人模型中,有一种属性方法来获取联系人的应付款总额。

@property
def amount_due(self):
    total_due = 0
    for due in self.amountgiven_set.all():
        total_due += due.amount_due

    return total_due

查询

ContactSerializer

class ContactMinSerializer(serializers.ModelSerializer):
    class Meta:
        model = Contact
        fields = (
            'id', 'first_name', 'amount_due', 'created', 'modified'
        )

由于amount_due中使用了ContactSerializer属性,因此每次调用联系人时都会调用amount_due属性,从而导致嵌套的数据库查询。

如何在应用程序中优化上述方案,以减少获取联系人或联系人列表时的数据库查询?特别是两个属性 amount_due total_returned

每次调用

amount_due()都会更新表中的 total_due 字段。

  

编辑2

class ContactViewSet(LoggingMixin, viewsets.ModelViewSet):
    serializer_class = ContactMinSerializer

    def get_queryset(self):
        return Contact.objects.filter(user=self.request.user).annotate(
            total_due=Sum(
                F('amountgiven_set__total_payable')
                - F('amountgiven_set__total_returned')
            )
        ).order_by('first_name')

1 个答案:

答案 0 :(得分:0)

You're looking for annotations.

您的视图集应如下定义查询集:

def create_foo(value):
   return Foo(value)

def concatenate(lhs, rhs):
   return list(lhs) + list(rhs)


concatenate(concatenate(create_foo(1), create_foo(2)), create_foo(3))

然后在序列化程序上定义一个MethodSerializer字段以解决该问题。

from django.db.models import F, Sum
Contact.objects.annotate(
    total_due=Sum(
        F('amountgiven_set__total_payable')
        - F('amountgiven_set__total_returned')
    )
)