python - 根据条件使用外键计数注释 Django 查询集

标签 python django django-models django-queryset django-aggregation

这是我的模型的简化版本:

class Airport(models.Model):
    iata = models.CharField()
    name = models.CharField()
    latitude = models.FloatField()
    longitude = models.FloatField()

class Flight(models.Model):
    origin = models.ForeignKey('Airport', related_name='origins')
    destination = models.ForeignKey('Airport', related_name='destinations')
    owner = models.ForeignKey(User)

给定一个 User,我想创建一个列表,其中包含出现在 origin 中的所有 Airport 对象他拥有的 Flight 对象的 destination 字段,每个字段都用相应数量的 Flight 对象进行注释。

例如,假设用户乘坐了 3 个航类:LAX-LHRLHR-CDGCDG-JFK。然后我想要一个返回以下对象的查询:

[LHR, id__count=2}, {CDG, id__count=2}, {LAX, id__count=1}, {JFK, id__count=1}]

在上面,三个字母代码代表Airport对象或其所有字段。

一般User可能有几千个,AirportFlight有几万个,求更多比使用 for 循环和 if 语句的明显解决方案更有效,最好是在单个数据库查询中。

我目前的进度是这个查询:

Airport.objects.filter(
    Q(origins__owner=user) | Q(destinations__owner=user)
)
.distinct()
.annotate(
    id__count=Count('origins', distinct=True) + Count('destinations', distinct=True)
).order_by('-id__count')

这只适用于一个用户,因为初始的 filter 只保留那些出现在他的航类中某处的机场。但是当他们是多个用户时,它显然会失败,因为计数包括每个用户的航类。我需要一些方法来只 Count 那些遵守特定属性的 Flight 对象,即 owner=user 其中 user是某个User对象。


编辑:阅读后this page in the Djnago documentation ,似乎将过滤器放在第一位应该可以根据需要进行这项工作。但事实并非如此,至少当我使用 Q 对象时是这样。我发现以下非常令人困惑的结果。

当我使用此查询时,即仅查看起点时,它会起作用,并且 num_origins 字段仅计算属于指定 user 的那些航类:

Airport.objects.filter(origins__owner=user).annotate(num_origins=Count('origins'))

(这不是我真正需要的,因为计数只包括起源于某个 Airport 的航类,但它确实正确地过滤了 User。)

但是,当我什么都不做,只是用两个 Q 对象替换单个过滤器时,即

Airport.objects.filter(Q(origins__owner=user) | Q(destinations__owner=user)).annotate(num_origins=Count('origins'))

现在它计算属于每个用户的航类!似乎注释在使用 Q 对象时“忘记”了过滤器。这是怎么回事?

最佳答案

我想你可以用条件表达式来实现:

from django.db.models import Case, When

Airport.objects.filter(
    Q(origins__owner=user) | Q(destinations__owner=user)
).annotate(
    num_origins=Count(
        Case(When(Q(origin__owner=user), then=1),output_field=CharField()),
    ),
    num_destinations=Count(
        Case(When(Q(destination__owner=user), then=1),output_field=CharField()),
    )
)

请注意,When 子句重复您最初执行的相同筛选。相反,这样做实际上可能更有效(您可能需要检查生成的 SQL 查询以找出答案):

Airport.objects.annotate(
    num_origins=Count(
        Case(When(Q(origin__owner=user), then=1), output_field=CharField()),
    ),
    num_destinations=Count(
        Case(When(Q(destination__owner=user), then=1),output_field=CharField()),
    )
).filter(Q(num_origins__gt=0) | Q(num_destinations__gt=0))

即注释所有航类,然后过滤掉计数为 0 的航类。

然后您可以在 Python 中添加 num_originsnum_destinations

如果您使用的是 Django 2,那么它仍然更简单,因为您可以将过滤器参数传递给 Count:

Airport.objects.annotate(
    num_origins=Count('origins', filter=Q(origin__owner=user), distinct=True),
    num_destinations=Count('destinations', filter=Q(destination__owner=user), disctinct=True)
).filter(Q(num_origins__gt=0) | Q(num_destinations__gt=0))

关于python - 根据条件使用外键计数注释 Django 查询集,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47823045/

相关文章:

python - Django Rest Framework - 发布外键

django - 如何为 manytomanyfield 设置默认值

sql-server - SQL Server 文件流与 django-mssql

python - 在views.py中调用models.py的方法而不创建实例

python 3.4 和 mysql 信号

python - 参数类型检查 Python

python - 如何在多核机器上加速 python 单元测试?

django - 测试 Jinja2 支持的 Django View 时如何访问 response.context

django - 如何在管理站点的组或用户权限列表中显示添加或更改 Django 模型的权限?

python - 运行 pip install --allow external 时 Mysql python 连接器不可用