如何将多个查询减少到只有一个查询?

时间:2014-08-01 20:14:18

标签: python django orm

如何使用djangos ORM构建单个查询,以便为每位玩家获得低于1.000的最高分?我使用的是django 1.7 rc2和python 3.4。

models.py:

from django.db import models

class Player(models.Model):
    pass

class Score(models.Model):
    player = models.ForeignKey(Player)
    score = models.PositiveIntegerField(default=0)

到目前为止我的解决方案:

tests.py

from django.test import TestCase

from bar.models import Player, Score


class BarTest(TestCase):
    def test_do_it(self):
        p1 = Player.objects.create()
        p2 = Player.objects.create()

        Score.objects.create(player=p1, score=100)
        Score.objects.create(player=p1, score=12000)
        Score.objects.create(player=p2, score=10000)
        Score.objects.create(player=p2, score=500)
        Score.objects.create(player=p2, score=900)

        players = Player.objects.all()
        for player in players:
            score = Score.objects.filter(player=player, score__lte=1000).order_by('score').last()
            if score:
                print(player.id, score.id, score.score)

我不想迭代python中的每个玩家并对每个玩家进行查询,而只用一个查询来解决这个问题(不用编写原始sql)。

2 个答案:

答案 0 :(得分:2)

使用select_related(),您可以一次性获取所有分数及其玩家:

from collections import defaultdict
scores = defaultdict(list)

# Fetch all scores (with score <= 1000), including their players.
for score in Score.objects.filter(score__lte=1000).select_related('player'):
    # Group scores by their player.
    scores[score.player.id].append(score)

# Get max score for each player.
for p in scores:
    max_score = max(scores[p], key=lambda s: s.score)
    print(p, max_score.id, max_score.score)

答案 1 :(得分:0)

怎么样:

all_scores = Score.objects.filter(score__lte=1000).order_by('player', '-score')
current_player = None
scores = []
for score in all_scores:
    if current_player != score.player:
        current_player = score.player
        scores.append(score)

只有一次迭代超过分数。排序由数据库完成。

修改

最后,我找到了一个解决方案:

Player.objects.filter(score__score__lte=1000
                     ).annotate(max_score=Max('score__score'))

构建此SQL查询:

SELECT
    "bar_player"."id",
    MAX("bar_score"."score") AS "max_score"
FROM
    "bar_player"
LEFT OUTER JOIN
    "bar_score"
ON
    ( "bar_player"."id" = "bar_score"."player_id" )
WHERE
    "bar_score"."score" <= 1000
GROUP BY
    "bar_player"."id"