最佳django manytomany查询

时间:2014-03-09 03:55:22

标签: django django-queryset

我在减少特定视图的查询数量方面遇到了麻烦。这是一个相当沉重的,但我相信它可以减少:

Profile:
  name = CharField()

Officers:
  club= ManyToManyField(Club, related_name='officers')
  title= CharField()

Club:
  name = CharField()
  members = ManyToManyField(Profile)

Election:
    club = ForeignKey(Club)
    elected = ForeignKey(Profile)
    title= CharField()
    when = DateTimeField()

俱乐部有会员和官员(总裁,比赛总监)。人们可以成为多个俱乐部的成员...... 在选举中选举官员,其结果将被存储。

鉴于一名球员,我怎样才能找到每个球员俱乐部最近当选的官员?

目前我有

clubs = Club.objects.filter(members=me).prefetch_related('officers')
for c in clubs:
  officers = c.officers.all()

  most_recent = Elections.objects.filter(club=c).filter(elected__in=officers).order_by('-when')[:1].get()
  print(c.name + ' elected ' + most_recent.name + ' most recently')

问题是循环查询,如果你是1个俱乐部的成员,那么它很好而且速度很快但是如果你加入了我的数据库,我会加入数据库。

修改 Nil的答案是我想做的,但没有得到对象。我真的不需要这个对象,但我确实需要另一个字段以及日期时间。如果查询有用:

Club.objects.annotate(last_election=Max('election__when'))

生成原始SQL

SELECT "organisation_club"."id", "organisation_club"."name", MAX("organisation_election"."when") AS "last_election" 
    FROM "organisation_club" 
    LEFT OUTER JOIN "organisation_election" ON ( "organisation_club"."id" = "organisation_election"."club_id" ) 
    GROUP BY "organisation_club"."id", "organisation_club"."name"

如果可能的话(或者主要是'ORM答案),我真的很喜欢ORM答案。

3 个答案:

答案 0 :(得分:5)

我相信这是你正在寻找的:

from django.db.models import Max, F

Election.objects.filter(club__members=me) \
                .annotate(max_date=Max('club__election_set__when')) \
                .filter(when=F('max_date')).select_related('elected')

可以在一个声明中再次关注前后关系,允许您为与当前选举俱乐部相关的任何选举注释max_date。 F类允许您根据SQL中的选定字段过滤查询集,包括通过注释,聚合,连接等添加的任何额外字段。

答案 1 :(得分:3)

您希望在SQL术语中定义here:查询Election表,按Club对它们进行分组,并仅保留每个俱乐部的最后一次选举。

现在,我们如何在Django ORM中翻译它?查看documentation,我们了解到我们可以使用注释来完成它。诀窍是你需要反过来思考。您希望在每个俱乐部的最后一次选举中注释(添加新数据)。这给了我们:

Club.objects.annotate(last_election=Max('election__when'))

# Use it in a for loop like that
for club in Club.objects.annotate(last_election=Max('election__when')):
    print(club, club.last_election)

可悲的是,这只会添加日期,但不会回答您的问题!您需要名称或完整的Club对象。我搜查了,我仍然不知道如何正确地做到这一点。如果一切都失败了,你仍然可以使用像第一个链接中的查询在Django中使用raw SQL query

答案 2 :(得分:1)

我能想到的最简单的方法是在应用程序级别部分过滤

如果你这样做

e = Election.objects.filter(club__members=me).select_related('elected')

e = me.club_set.election_set.select_related('elected')

这是一个单一的查询,它应该收回会员me所在的所有俱乐部所发生的所有选举。然后你可以使用python来获取最新的日期。当然,如果每个俱乐部有很多选举,你最终会获取的数据远远超过使用的数据。

应该在两个查询中执行此操作的另一种方法:

# Get all member's clubs & most recent election
clubs = Club.objects.filter(members=me).annotate(last_election=Max('election__when'))
# Create filters for election based on the club id and the latest election time
election_Q = [Q(club__id=c.id) & Q(when=c.last_election) for c in clubs]
# Combine filters with an OR
election_filter = reduce(lambda f1, f2: f1 | f2, election_Q)

# Get elections restricting by specific clubs & election date
elections = Election.objects.filter(election_filter).select_related('elected')

for e in elections:
    print '%s elected %s most recently at %s' % (e.club.name, e.elected, e.when)

这是基于@Nil的方法构建的,并使用其结果在python中构建查询,然后将其提供给第二个查询。但是,SQL语句的大小存在限制,如果成员所在的俱乐部很多,那么您可能会达到限制。虽然限制相当高,但我在单个INSERT语句中导入大型数据集时只能达到它,所以我认为它应该适用于您的目的。

很抱歉,我无法想到Django ORM可以使用单个SQL查询将它们链接在一起的方式。对于复杂的查询,Django ORM实际上是quite limited所以如果你真的需要效率,我认为编写原始SQL查询可能是最好的。