在 django 中向 QuerySet 添加 ManyToMany 额外字段

Posted

技术标签:

【中文标题】在 django 中向 QuerySet 添加 ManyToMany 额外字段【英文标题】:Adding ManyToMany extra field to QuerySet in django 【发布时间】:2012-02-04 09:29:22 【问题描述】:

假设我有以下模型:

class Thing(models.Model):
    name = models.CharField(max_length=100)
    ratings = models.ManyToManyField('auth.User', through='Rating')

class Rating(models.Model):
    user = models.ForeignKey('auth.User')
    thing = models.ForeignKey('Thing')
    rating = models.IntegerField()

所以我有很多东西,每个用户都可以评价每件事。我还有一个视图显示所有事物的列表(它们的数量很大)以及用户分配给每个事物的评级。我需要一种从数据库中检索所有数据的方法:具有附加字段 user_rating 的事物对象最多取自一个(因为我们有一个固定的用户)相关的评级对象。

简单的解决方案如下所示:

things = Thing.objects.all()
for thing in things:
    try:
        thing.user_rating = thing.ratings.objects.get(user=request.user).rating
    except Rating.DoesNotExist:
        thing.user_rating = None

但是这种方法的缺陷很明显:如果我们有 500 个东西,我们将对数据库发出 501 个请求。每页。每个用户。这是该网站浏览次数最多的页面。这个任务很容易用 SQL JOIN 解决,但实际上我有更复杂的模式,我肯定会从 Django 模型框架中受益。所以问题是:有可能以 Django 方式进行吗?考虑到这样的任务很常见,如果不是这样就真的很奇怪了。

据我了解,annotate()select_related() 都不会在这里帮助我。

【问题讨论】:

【参考方案1】:

因为您打算在一页中显示所有内容。我可以想到这种方法。你可以试试这个:

获取当前用户给出的所有评分并获取所有事物。

现在尝试像这样创建一个字典:

thing_dict = 

for thing in Thing.objects.all():
    thing_dict[thing] = None
for rating in Rating.objects.filter(user = request.user):
    thing_dict[rating.thing] = rating

现在 thing_dict 包含模型 Thing 的所有条目作为键,并将其评级作为其值。

可能不是最好的方法。我很想看看其他人的回答。

【讨论】:

您仍然得到每个用户的查询而不是聚合。 好的,但它肯定不是完美的。如果事情被分成几页怎么办?比如说,有 10000 个东西,页面包含 100 个条目。第二个循环必须查看所有可能的 10000 个评分。 @Tom 至少有 2 个查询而不是 n+1 @user285176,如果我们将其拆分为多个页面,这肯定是个坏主意。根据用户对事物评分的数量,我们可以考虑以非规范化的方式存储评分信息。【参考方案2】:

我想你应该试试这个: https://docs.djangoproject.com/en/1.3/ref/models/querysets/#extra

例子

result = Thing.objects.all().extra(select='rating': 'select rating from ratings where thing_id = id')

您的结果集为每个“事物”对象获取一个新字段“评级”。

我在最近的一个项目中使用了这种方法。它产生一个复杂的查询而不是 n+1 个查询。

希望这会有所帮助:)

【讨论】:

看起来好多了,谢谢。它将执行嵌套 SELECT,我理解正确吗?所以基本上数据库执行相同数量的工作,JOIN 仍然是可取的。 您可以通过打印connection.queries来忽略查询,还有另一种方法,您可以编写一个自定义的原始sql查询(使用connection.cursor) 作业量不同,一个事务,一个数据库连接,一个嵌套查询(而不是n+1个查询),比较快 F 表达式不是最安全的方式吗?见docs.djangoproject.com/en/dev/ref/models/expressions/… @radtek 自从我提出解决方案以来已经三年多了。如果我得到正确的文档,F 表达式被添加到 django 1.8(5 个版本以后)中。如果有一个现代解决方案可以产生相同的结果并且更优雅,那么非常欢迎您创建一个替代答案。

以上是关于在 django 中向 QuerySet 添加 ManyToMany 额外字段的主要内容,如果未能解决你的问题,请参考以下文章

如何向 Django QuerySet 添加附加列

在django中动态添加参数到queryset过滤器调用[重复]

如何在 Django 中向 ManyToManyField 添加条目?

django的queryset和objects对象

如何在 Django 中向 bootstrap4 添加松脆的表单?

如何在Django中向Python路径添加位置