Django ORM:选择相关集

时间:2009-05-12 14:50:25

标签: python django orm

说我有2个型号:

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

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

鉴于Poll对象,我可以通过以下方式查询其选择:

poll.choice_set.all()

但是,是否有一个实用程序函数来查询一组Poll中的所有选项?

实际上,我正在寻找类似以下的东西(不支持,我不寻求它是怎样的):

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 个答案:

答案 0 :(得分:18)

时间已过,现在Django 1.4中提供了此功能,并引入了prefetch_related() QuerySet函数。此函数有效地执行建议的qbind函数执行的操作。即。执行两个查询,并且连接发生在Python域中,但现在由ORM处理。

原始查询请求现在变为:

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

如以下代码示例所示,polls QuerySet可用于获取每ChoicePoll个对象,而无需进一步的数据库命中:

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

答案 1 :(得分:15)

我认为你所说的是,“我希望所有选择都能进行一系列民意调查。”如果是这样,试试这个:

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

答案 2 :(得分:12)

更新:自Django 1.4以来,此功能已内置:请参阅prefetch_related

第一个答案:在你编写一个正在运行的应用程序,对其进行分析并证明N个查询实际上是数据库和负载场景的性能问题之前,不要浪费时间编写像qbind这样的东西。

但也许你已经做到了。所以第二个答案:qbind()做了你需要做的事情,但是如果打包在一个自定义的QuerySet子类中,并且伴随着一个返回自定义QuerySet实例的Manager子类,它会更加惯用。理想情况下,您甚至可以使它们具有通用性,并可以重复使用任何反向关系。然后你可以做类似的事情:

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

有关Manager / QuerySet技术的示例,请参阅this snippet,它解决了类似的问题,但对于通用外键的情况,不是反向关系。将qbind()函数的内容与其中显示的结构相结合,为您的问题提供一个非常好的解决方案并不是一件难事。

答案 3 :(得分:1)

我认为你要做的是对子数据的“急切加载”这个术语 - 意味着你正在为每个Poll加载子列表(choice_set),但是在第一次查询DB时都是如此,所以你不要以后不得不做一堆查询。

如果这是正确的,那么您要找的是'select_related' - 请参阅https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-related

我注意到你尝试'select_related'但它没有用。你可以尝试做'select_related'然后再做过滤器。这可能会解决它。


更新:这不起作用,请参阅下面的评论。