【问题标题】:Django multiple annotate with Sum get wrong answerDjango 用 Sum 多次注释得到错误的答案
【发布时间】:2021-09-03 21:49:10
【问题描述】:

我正在尝试将.annotate() 与多个Sum() 一起使用,但我的计算错误。 我读过我应该使用Subquery,但我没有完成它,也许我以错误的方式使用它(因为这是第一次)或者它不能解决我的问题。

#managers.py

class DonationQuerySet(QuerySet):

    def completed(self):
        return self.with_donations_stats().filter(
            amount__lte=F('total_donation'), deleted_at=None)

    def not_completed(self):
        return self.with_donations_stats().filter(
            amount__gt=F('total_donation'), deleted_at=None)

    def with_donations_stats(self):
        return self.annotate(
            wallet_donation=Coalesce(Sum('wallet_transaction__amount'), 0),
            normal_donation=Coalesce(Sum('transactions__amount'), 0),
            total_donation=F('wallet_donation') + F('normal_donation'))


class DonationManager(Manager):
    def get_queryset(self):
        return DonationQuerySet(self.model, using=self._db)

    def completed(self):
        return self.get_queryset().completed()

    def not_completed(self):
        return self.get_queryset().not_completed()

    def with_donations_stats(self):
        return self.get_queryset().with_donations_stats()
#models.py

class Transaction(models.Model):
    def __str__(self):
        return self.payment_id + ' - ' + self.status

    condition = models.ForeignKey('condition.Condition', related_name='transactions',
                                  on_delete=models.CASCADE) 
    
    amount = models.IntegerField(null=False, blank=False)

class WalletTransaction(AbstractBaseModel):
    condition = models.ForeignKey("condition.Condition", on_delete=models.SET_NULL, related_name='wallet_transaction', null=True)
    amount = models.PositiveIntegerField()

    def __str__(self):
        return f"{self.id}"

class Condition(models.Model):
    def __str__(self):
        return str(self.id)  # .zfill(10)
    STATUS_PUBLISHED = "Published"
    STATUS_CHOICES = (
        (STATUS_PUBLISHED, 'Published'),)
    status = models.CharField(max_length=25, choices=STATUS_CHOICES, db_index=True)

    donations = DonationManager()

在我看来,我正在查询已发布的条件 条件 =

Condition.donations.filter(status=Condition.STATUS_PUBLISHED).with_donations_stats().order_by(
            '-id')

更新 最终查询是SELECT "conditions"."id", "conditions"."user_id", "conditions"."association_id", "conditions"."nid", "conditions"."amount", "conditions"."donation", "conditions"."nid_type", "conditions"."first_name", "conditions"."father_name", "conditions"."last_name", "conditions"."nationality", "conditions"."gender", "conditions"."mobile", "conditions"."region", "conditions"."city", "conditions"."district", "conditions"."dob", "conditions"."introduction_file", "conditions"."disease_validation_file", "conditions"."medical_report_file", "conditions"."treatment_plan_file", "conditions"."cost_file", "conditions"."case_report_file", "conditions"."invoices_file", "conditions"."payment_file", "conditions"."generated_pdf_file", "conditions"."recovery_report_file", "conditions"."report_date", "conditions"."issued_place", "conditions"."specialization", "conditions"."disease_type", "conditions"."action_type", "conditions"."case_type", "conditions"."treatment_entity", "conditions"."accommodation_type", "conditions"."family_members", "conditions"."income_avg", "conditions"."medical_evaluation_status", "conditions"."researcher_opinion", "conditions"."rejection_reason", "conditions"."justification", "conditions"."insurance_company_name_ar", "conditions"."insurance_company_name_en", "conditions"."insurance_company_id", "conditions"."insurance_beneficiary_number", "conditions"."insurance_beneficiary_type", "conditions"."insurance_class", "conditions"."insurance_expiry", "conditions"."insurance_limit", "conditions"."insurance_policy", "conditions"."status", "conditions"."created_at", "conditions"."updated_at", "conditions"."published_at", "conditions"."deleted_at", "conditions"."image", "conditions"."last_donation_at", COALESCE((SELECT SUM(U0."amount") AS "amount_sum" FROM "wallets_wallettransaction" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."id" ORDER BY U0."id" DESC LIMIT 1), 0) AS "wallet_donation", COALESCE((SELECT SUM(U0."amount") AS "amount_sum" FROM "transactions" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."condition_id" LIMIT 1), 0) AS "normal_donation", (COALESCE((SELECT SUM(U0."amount") AS "amount_sum" FROM "wallets_wallettransaction" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."id" ORDER BY U0."id" DESC LIMIT 1), 0) + COALESCE((SELECT SUM(U0."amount") AS "amount_sum" FROM "transactions" U0 WHERE U0."condition_id" = ("conditions"."id") GROUP BY U0."condition_id" LIMIT 1), 0)) AS "total_donation" FROM "conditions" WHERE "conditions"."status" = Published ORDER BY "conditions"."id" DESC

注意:我在另一个视图中使用相同的注释会产生相同的结果。

【问题讨论】:

  • 显示您对子查询的尝试...(此外,您在问题wallet_transactiontransactions 中没有显示足够的信息,应该是一些相关模型,在这种情况下您应该至少显示一些他们的相关代码,并显示您为其编写此管理器的模型)
  • 我添加了一些 wallet_transaction 和 transactions 模型,还添加了我使用注释并得到错误总和的查询。在我看来,让我们忽略我对子查询的尝试

标签: python django django-models orm


【解决方案1】:

您偶然发现了Combining multiple aggregations [Django docs]yield the wrong results 的问题,因为使用的是联接而不是子查询。

既然要对关系进行聚合,Django 会进行连接,并且在多个关系上进行聚合时会有多个连接,结果当然是错误的。因此我们需要使用subqueries进行注释:

from django.db.models import OuterRef, Subquery


class DonationQuerySet(QuerySet):
    ...
    
    def with_donations_stats(self):
        # Subquery for wallet donation
        # Added order_by because it appears you have some default ordering
        wallet_donation = WalletTransaction.objects.filter(
            condition=OuterRef('pk')
        ).order_by().values('condition').annotate(amount_sum=Sum('amount')).values('amount_sum')[:1]
        
        # Subquery for normal donation
        normal_donation = Transaction.objects.filter(
            condition=OuterRef('pk')
        ).values('condition').annotate(amount_sum=Sum('amount')).values('amount_sum')[:1]
        
        return self.annotate(
            wallet_donation=Coalesce(Subquery(wallet_donation), 0),
            normal_donation=Coalesce(Subquery(normal_donation), 0),
            total_donation=F('wallet_donation') + F('normal_donation')
        )

【讨论】:

  • 我注意到wallet_donation value 总是返回最新的捐款金额而不是总和
  • 我已经复制了with_donations_stats(),因为它与normal_donation 一起工作得很好我注意到WalletTransaction 表中的条件我在Condition.donations.filter(status=Condition.STATUS_PUBLISHED).with_donations_stats().order_by( '-id') 中使用with_donations_stats() 没有更多你怎么看?
  • @AhmedSamy 您可以将print(queryset.query) 的输出和您的确切查询集代码添加到您的问题中吗? (queryset 是您正在进行的查询)
  • 我已将其添加到问题中
猜你喜欢
  • 2010-11-18
  • 2021-09-07
  • 1970-01-01
  • 1970-01-01
  • 2022-07-06
  • 2019-05-06
  • 2016-10-23
  • 1970-01-01
  • 2020-06-23
相关资源
最近更新 更多