我可以聚合多个值(多对多->val_a,val_b)吗?

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

我正在尝试计算共享公共相关对象+公共值的项目的查找。作为非正式的总结,我想使用某种形式:

my_queryset.values(my_related_obj, my_date).annotate(...)

--为了获得每个

(related_obj, date)
对的查询集中匹配项的 Sum 聚合。

本例中的

related_obj
是两个连接——一个多对多关系,然后是一个外键。因此,肯定存在具有相关对象的多个值的查询集项,或者通过不同的中间对象到达相同的相关对象。这看起来应该是可行的,但无论我尝试什么,我都会得到不正确的结果。我无法让分组达到我想要的效果。

这是一个简单的例子:我将

Persons
分配给
Teams
;我在特定日期有(多个)
ExpenseReports
签署了
Persons
。 我想要一个查询,查找每对
Team
date
,该团队在该日期签署的总费用。

这是我的模型:

class MyTeam(models.Model):
    name = models.CharField()

class MyPerson(models.Model):
    name = models.CharField()
    team = models.ForeignKey(MyTeam, on_delete=models.CASCADE)

class ExpenseReport(models.Model):
    expense_paid = models.FloatField()
    expense_date = models.DateField()
    persons = models.ManyToManyField(MyPerson)

这里有一些简单的数据——两个日期的费用报告。

Appa
Alex
Team A
Barbara
Bob
已开启
Team B
:

[2024-11-01] 1.0 paid by [<MyPerson: Person <Alex>>, <MyPerson: Person <Appa>>]    <-- Team A
[2024-11-01] 10.0 paid by [<MyPerson: Person <Barbara>>, <MyPerson: Person <Bob>>] <-- Team B
[2024-11-05] 100.0 paid by [<MyPerson: Person <Barbara>>]                          <-- Team B
[2024-11-05] 1000.0 paid by [<MyPerson: Person <Alex>>, <MyPerson: Person <Bob>>]  <-- Teams A and B

有了这些数据,我正在寻找的结果是:

{'expense_date': datetime.date(2024, 11, 1), 'persons__team__name': 'A Team', 'total_expense': 1.0}
{'expense_date': datetime.date(2024, 11, 1), 'persons__team__name': 'B Team', 'total_expense': 10.0}
{'expense_date': datetime.date(2024, 11, 5), 'persons__team__name': 'A Team', 'total_expense': 1000.0}
{'expense_date': datetime.date(2024, 11, 5), 'persons__team__name': 'B Team', 'total_expense': 1100.0}

我尝试过的事情

有一个明显的幼稚实现——这是不正确的,因为它没有考虑重复的行:

    reports_qs = ExpenseReport.objects.all()
    rows = reports_qs.values("expense_date", "persons__team__name").annotate(total_expense=Sum("expense_paid"))

很容易看出这些结果是错误的——来自同一团队的两名团队成员的值会加倍:

{'expense_date': datetime.date(2024, 11, 1), 'persons__team__name': 'B Team', 'total_expense': 20.0} <-- doubled
{'expense_date': datetime.date(2024, 11, 5), 'persons__team__name': 'B Team', 'total_expense': 1100.0}
{'expense_date': datetime.date(2024, 11, 5), 'persons__team__name': 'A Team', 'total_expense': 1000.0}
{'expense_date': datetime.date(2024, 11, 1), 'persons__team__name': 'A Team', 'total_expense': 2.0}  <-- doubled

但我认为解决方案是使用子查询,但这也不起作用:

    reports_qs = ExpenseReport.objects.all()
    subquery = (
        ExpenseReport.objects.filter(
            expense_date=OuterRef("expense_date"),
            persons__team__name=OuterRef("persons__team__name"),
        )
        .values("expense_date", "persons__team__name")
        .annotate(total_expense=Sum("expense_paid"))
        .values("total_expense")
    )
    rows = reports_qs.values("expense_date", "persons__team__name").annotate(total_expense=subquery[:1])

这给出了结果:

{'expense_date': datetime.date(2024, 11, 1), 'persons__team__name': 'A Team', 'total_expense': 2.0} <-- doubled
{'expense_date': datetime.date(2024, 11, 1), 'persons__team__name': 'A Team', 'total_expense': 2.0} <-- doubled
{'expense_date': datetime.date(2024, 11, 1), 'persons__team__name': 'B Team', 'total_expense': 20.0} <-- doubled
{'expense_date': datetime.date(2024, 11, 1), 'persons__team__name': 'B Team', 'total_expense': 20.0} <-- doubled
{'expense_date': datetime.date(2024, 11, 5), 'persons__team__name': 'B Team', 'total_expense': 1100.0}
{'expense_date': datetime.date(2024, 11, 5), 'persons__team__name': 'B Team', 'total_expense': 1100.0}
{'expense_date': datetime.date(2024, 11, 5), 'persons__team__name': 'A Team', 'total_expense': 1000.0}

--以及其他绝望的尝试坚持

distinct()
有帮助的地方并没有解决问题。

我还可以看到,我从

values().annotate()
结构中得到的结果与通过
aggregate()
调用得到的结果完全不同:

expense_date, team_name = example_report.expense_date, example_report.persons.first().team.name

    # values().annotate()
    res1 = (
        ExpenseReport.objects.filter(
            expense_date=expense_date,
            persons__team__name=team_name,
        )
        .values("expense_date", "persons__team__name")
        .annotate(total_expense=Sum("expense_paid"))
        .values("total_expense")
    )

    # aggregate()
    res2 = (
        ExpenseReport.objects.filter(
            expense_date=expense_date,
            persons__team__name=team_name,
        )
        .distinct()
        .aggregate(total_expense=Sum("expense_paid"))
    )

结果:

> res1=<QuerySet [{'total_expense': 2.0}]>  # ...doubled yet again
> res2={'total_expense': 1.0}               # correct

是否可以在 Django ORM 中执行我尝试的聚合?我做错了什么?

django django-orm django-aggregation
1个回答
0
投票

如果

.person
的所有
ExpenseReport
始终保证相同
Team
,我们可以使用
Subquery
表达式[Django-doc]
:

来做到这一点
from django.db.models import OuterRef, Subquery, Sum

ExpenseReport.objects.values(
    'expense_date',
    team_id=Subquery(
        MyPerson.objects.filter(
            expensereport=OuterRef('pk')
        ).values('team_id')[:1]
    ),
).annotate(total_expense=Sum('expense_paid')).order_by('expense_date', 'team_id')

问题更多的是,如果费用分摊给多个团队该怎么办。

© www.soinside.com 2019 - 2024. All rights reserved.