Django 使用注释更新查询集
Posted
技术标签:
【中文标题】Django 使用注释更新查询集【英文标题】:Django update queryset with annotation 【发布时间】:2011-04-08 20:09:21 【问题描述】:我想使用带注释的值更新查询集中的所有行。
我有一个简单的模型:
class Relation(models.Model):
rating = models.IntegerField(default=0)
class SignRelation(models.Model):
relation = models.ForeignKey(Relation, related_name='sign_relations')
rating = models.IntegerField(default=0)
我想避免这段代码:
for relation in Relation.objects.annotate(total_rating=Sum('sign_relations__rating')):
relation.rating = relation.total_rating or 0
relation.save()
并使用以下内容在 一个 SQL 请求中进行更新:
Relation.objects.update(rating=Sum('sign_relations__rating'))
不起作用:
TypeError: int() argument must be a string or a number, not 'Sum'
或
Relation.objects.annotate(total_rating=Sum('sign_relations__rating')).update(rating=F('total_rating'))
也不行:
DatabaseError: missing FROM-clause entry for table "relations_signrelation"
LINE 1: UPDATE "relations_relation" SET "rating" = SUM("relations_si...
是否可以为此目的使用 Django 的 ORM?文档中没有关于同时使用 update() 和 annotate() 的信息。
【问题讨论】:
我不确定这在纯 SQL 中是否可行?在 SQL 中进行 UPDATE 时,我认为不可能加入其他表。 是的,这是可能的 - 通过子查询,例如。UPDATE t1 SET a = (SELECT SUM(c) from t2 where t2.b=t1.b)
;
你真正想做什么?您是否尝试总结“SigningRelation”表中“rating”列的所有值,然后将其保存为“Relation”表中的新行?
【参考方案1】:
对于 Django 1.11+,您可以使用 Subquery:
from django.db.models import OuterRef, Subquery, Sum
Relation.objects.update(
rating=Subquery(
Relation.objects.filter(
id=OuterRef('id')
).annotate(
total_rating=Sum('sign_relations__rating')
).values('total_rating')[:1]
)
)
此代码生成与Tomasz Jakub Rup 提出的相同的SQL 代码,但没有使用RawSQL 表达式。 由于SQL injection) 的可能性,Django 文档警告不要使用 RawSQL。
更新
我根据这个答案发表了一篇文章并进行了更深入的解释:Updating a Django queryset with annotation and subquery on paulox.net
【讨论】:
哇,update
和 Subquery
超级酷,感谢您指出这种模式!
很高兴您发现我的回答很有用
我建议使用.values_list('total_rating', flat=True)
直接提取值,而不是使用我一开始不理解的[:1]
。
这是一个很棒的 sn-p,但我通过了解释,查询计划在一个相当大的表上似乎很慢。另一种选择是使用 ORM 生成 subqery SQL,使用数据创建临时表,然后编写手动更新查询。在我的情况下,它需要多分钟的更新时间才 1 分钟。
我在你的博客上也注意到了这一点,有一个新包pypi.org/project/django-sql-utils 可以让你更轻松地进行子查询Relation.objects.update(rating=SubqueryAvg('sign_relations__rating'))
【参考方案2】:
UPDATE
语句不支持GROUP BY
。参见例如PostgreSQL Docs,SQLite Docs。
你需要这样的东西:
UPDATE relation
SET rating = (SELECT SUM(rating)
FROM sign_relation
WHERE relation_id = relation.id)
DjangoORM 中的等价物:
from django.db.models.expressions import RawSQL
Relation.objects.all(). \
update(rating=RawSQL('SELECT SUM(rating) FROM signrelation WHERE relation_id = relation.id', []))
或:
from django.db.models import F, Sum
from django.db.models.expressions import RawSQL
Relation.objects.all(). \
update(rating=RawSQL(SignRelation.objects. \
extra(where=['relation_id = relation.id']). \
values('relation'). \
annotate(sum_rating=Sum('rating')). \
values('sum_rating').query, []))
【讨论】:
您是否能够运行上一次推荐?.filter(relation=F('relation__pk'))
不等于 WHERE (relation.id = relation.id)
,因此不加入任何东西吗?
感谢更新。 .extra()
子句是金子!【参考方案3】:
postgres 的解决方法:
with connection.cursor() as cursor:
sql, params = qs.query.sql_with_params()
cursor.execute("""
WITH qs AS ()
UPDATE foo SET bar = qs.bar
FROM qs WHERE qs.id = foo.id
""".format(sql), params)
【讨论】:
谁给了你一个减号,仅仅因为使用 mysql?太刻薄了。【参考方案4】:您可以定义自己的自定义对象管理器:
class RelationManager(models.Manager):
def annotated(self,*args,*kwargs):
queryset = super(RelationManager,self).get_queryset()
for obj in queryset:
obj.rating = ... do something ...
return queryset
class Relations(models.Model):
rating = models.IntegerField(default=0)
rating_objects = RelationManager()
然后在你的代码中:
q = Realation.rating_objects.annotated()
添加 args/kwargs 以自定义此管理器返回的内容。
【讨论】:
【参考方案5】:如果你想避免多次调用数据库,你应该使用transaction.atomic
。
阅读有关 Django 文档的更多信息:https://docs.djangoproject.com/en/1.9/topics/db/transactions/#controlling-transactions-explicitly
【讨论】:
transaction.atomic
不会减少调用次数,它只是强制整个事务要么完全成功,要么失败而不做任何修改。【参考方案6】:
你真的不能这样做。看看the code 了解update
和follow it through 了解一些精读。
老实说,在 Manager 定义中放置这样的内容有什么问题?将您不想放在视图中的那 3 行放入经理中,必要时调用该经理。此外,您所做的“魔术”要少得多,当下一个开发人员查看您的代码时,他们将不必求助于几个 WTF .. :)
另外,我很好奇,看起来你可以使用SQL Join with UPDATE statements,但这是一些经典的 SQL 黑客技术。所以如果你愿意,你可以使用 Django 的原始 SQL 功能;)
【讨论】:
“老实说,在 Manager 定义中放置这样的内容有什么问题?”这可能是发送到数据库的 lot 请求。他特别要求“一个 SQL 请求”。另外,我认为他尝试的示例很容易阅读,根本不像魔术。 最近我不得不手动编写一个 SQL 查询,因为正确编写它需要 200 毫秒才能执行,而使用更新外观时我必须等待 30 分钟。这就是原因。以上是关于Django 使用注释更新查询集的主要内容,如果未能解决你的问题,请参考以下文章