以下是我的模型的简化版本:
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
,我想创建Airport
origin
或destination
字段中显示的所有Flight
个对象的列表他拥有的对象,每个对象都带有相应数量的Flight
个对象。
例如,假设用户使用了3个航班:LAX-LHR
,LHR-CDG
和CDG-JFK
。然后我想要一个返回以下对象的查询:
[LHR, id__count=2}, {CDG, id__count=2}, {LAX, id__count=1}, {JFK, id__count=1}]
在上文中,三个字母代码代表Airport
个对象或其所有字段。
通常,可能有数千User
s和成千上万Airport
s和Flight
s,所以我正在寻找比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对象时,注释会“忘记”过滤器。这是怎么回事?
答案 0 :(得分:2)
I think you can achieve this with conditional expressions:
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, else=0)),
),
num_destinations=Count(
Case(When(Q(destination__owner=user), then=1, else=0)),
)
)
Note that the When
clause is repeating the same filter that you do initially. It might actually be more efficient to do this instead (you probably need to inspect the resulting SQL query to find out):
Airport.objects.annotate(
num_origins=Count(
Case(When(Q(origin__owner=user), then=1, else=0)),
),
num_destinations=Count(
Case(When(Q(destination__owner=user), then=1, else=0)),
)
).filter(Q(num_origins__gt=0) | Q(num_destinations__gt=0))
i.e., annotate all flights, and then filter out the ones where the count was 0.
You can then add up num_origins
and num_destinations
in Python.
If you are using Django 2, then it is simpler still because you can pass a filter argument to 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))
答案 1 :(得分:0)
# This is all of the distinct flights of your users.
distinct_flights = Flight.objects.filter(owner__in=[user1.id, user2.id]).distinct().values_list('origin','destination')
# This is all of the airports included in the flights above.
Airport.objects.filter(
Q(origins__in=distinct_flights['origin'])||
Q(destination__in=distinct_flights['destination'])
)
# The rest is annotation from those airports as you did before. You can annotate it on the above query again.