Django ORM:选择相关集

22 投票
4 回答
25028 浏览
提问于 2025-04-15 11:32

假设我有两个模型:

class Poll(models.Model):
    category = models.CharField(u"Category", max_length = 64)
    [...]

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    [...]

给定一个投票对象,我可以用以下方式查询它的选项:

poll.choice_set.all()

但是,有没有什么工具函数可以从一组投票中查询所有选项呢?

其实,我想要的功能类似于下面这个(不过这个功能并不支持,我也不想知道怎么实现):

polls = Poll.objects.filter(category = 'foo').select_related('choice_set')
for poll in polls:
    print poll.choice_set.all() # this shouldn't perform a SQL query at each iteration

我写了一个(很丑的)函数来帮我实现这个功能:

def qbind(objects, target_name, model, field_name):
    objects = list(objects)
    objects_dict = dict([(object.id, object) for object in objects])
    for foreign in model.objects.filter(**{field_name + '__in': objects_dict.keys()}):
        id = getattr(foreign, field_name + '_id')
        if id in objects_dict:
            object = objects_dict[id]
            if hasattr(object, target_name):
                getattr(object, target_name).append(foreign)
            else:
                setattr(object, target_name, [foreign])
    return objects

这个函数的使用方法如下:

polls = Poll.objects.filter(category = 'foo')
polls = qbind(polls, 'choices', Choice, 'poll')
# Now, each object in polls have a 'choices' member with the list of choices.
# This was achieved with 2 SQL queries only.

有没有什么更简单的方法是Django已经提供的?或者至少,有没有一个代码片段可以更好地完成同样的事情。

你们通常是怎么处理这个问题的呢?

4 个回答

15

我觉得你想表达的是,“我想要一组投票的所有选项。”如果是这样的话,可以试试这个:

polls = Poll.objects.filter(category='foo')
choices = Choice.objects.filter(poll__in=polls)
23

时间过去了,这个功能现在在Django 1.4中可用了,主要是通过引入了一个叫做 prefetch_related() 的查询集功能。这个功能实际上做的事情和之前建议的 qbind 函数是一样的。也就是说,它会执行两个查询,然后在Python中进行连接,但现在这个过程是由ORM来处理的。

原来的查询请求现在变成了:

polls = Poll.objects.filter(category = 'foo').prefetch_related('choice_set')

在下面的代码示例中,可以看到 polls 查询集可以用来获取每个 Poll 对应的所有 Choice 对象,而不需要再去数据库查询:

for poll in polls:
    for choice in poll.choice_set:
        print choice
17

更新: 从Django 1.4开始,这个功能已经内置了:请查看prefetch_related

首先,别浪费时间去写像qbind这样的东西,直到你已经写出了一个能正常工作的应用程序,分析过它的性能,并证明N个查询确实是你数据库和负载场景中的一个性能问题。

但也许你已经做过这些了。那么第二个建议是:qbind()能满足你的需求,但如果把它放在一个自定义的QuerySet子类中,并配上一个返回这个自定义QuerySet实例的Manager子类,那就更符合Django的风格了。理想情况下,你甚至可以让它们变得通用,可以在任何反向关系中重复使用。这样你就可以做类似这样的事情:

Poll.objects.filter(category='foo').fetch_reverse_relations('choices_set')

关于Manager/QuerySet技术的一个例子,可以参考这个代码片段,它解决了类似的问题,但针对的是通用外键,而不是反向关系。将你的qbind()函数的核心部分与那里的结构结合起来,应该不难做出一个很好的解决方案。

撰写回答