Django ORM:选择相关集

Posted

技术标签:

【中文标题】Django ORM:选择相关集【英文标题】:Django ORM: Selecting related set 【发布时间】:2009-05-12 14:50:25 【问题描述】:

假设我有 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 是否已经提供了一些更简单的东西?或者至少,一个 sn-p 以更好的方式做同样的事情。

你通常如何处理这个问题?

【问题讨论】:

也许你的 qbind 函数是可以做到的最好的。但将其打包在自定义管理器中可能是有意义的 - docs.djangoproject.com/en/dev/topics/db/managers/#id2 【参考方案1】:

时间已经过去了,随着 prefetch_related() QuerySet 函数的引入,这个功能现在在 Django 1.4 中可用。此函数有效地执行建议的qbind 函数执行的操作。 IE。执行了两个查询,连接发生在 Python 领域,但现在由 ORM 处理。

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

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

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

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

【讨论】:

+1 进行跟进。谷歌偶然发现此页面的任何人的最佳解决方案。 我使用 Django 1.6,并且在 python shell 中使用时得到类型错误相关的管理器对象不可迭代。我做了和 Frederic 完全相同的事情:Choice 有外键投票,所以两者之间存在 1 到 n 的关系。民意调查和选择 @Timo 你应该打电话给poll.choice_set.all()【参考方案2】:

更新:从 Django 1.4 开始,此功能已内置:请参阅prefetch_related。

第一个答案:不要浪费时间编写类似 qbind 的东西,除非您已经编写了一个工作应用程序,对其进行了概要分析,并证明 N 次查询实际上是您的数据库和负载方案的性能问题。

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

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

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

【讨论】:

【参考方案3】:

我认为您的意思是,“我想要一组民意调查的所有选项。”如果是这样,试试这个:

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

【讨论】:

+1 我不知道这个功能!多么优雅! 这是我在qbind函数开始时所做的。但实际上我想要一组选择 per 民意调查,而不是整套选择。例如,如果我想在模板上显示民意调查列表,以及每个人的选择,我不想为每个民意调查访问数据库。 qbind 函数的重点是将您的 pollschoices 数据连接在一起以实现这一目标。【参考方案4】:

我认为您正在尝试做的是术语“急切加载”子数据 - 这意味着您正在为每个轮询加载子列表 (choice_set),但所有这些都在对数据库的第一个查询中,因此您不需要以后不必进行大量查询。

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

我注意到您尝试了“select_related”,但没有成功。你可以尝试做'select_related'然后过滤。这可能会解决它。


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

【讨论】:

select_related 如果 a 正在查询 Choice 并希望预加载每个相应的民意调查,这将很有用。但是在这里,我想要 select_related 不支持的相反(想想相应的 SQL 查询,如果不复制大量数据,它不能在一个查询中完成。)这应该用 2 个查询来完成。 是的,你的权利。很抱歉没有看到。由于 'choice_set' 在查询被评估之前不可用,因此它甚至无法识别它的存在。

以上是关于Django ORM:选择相关集的主要内容,如果未能解决你的问题,请参考以下文章

使用 Django 的 ORM 和 Django Rest Framework 序列化嵌套关系的查询集的正确方法?

Django ORM 类似于 Pony ORM 中的选择查询

动态选择 django orm 列

使用 django ORM 进行高级选择

Django ORM查询无法选择新对象

使用 Django ORM 进行选择性事务管理