Django使用注释更新queryset

ram*_*sus 35 django django-models django-queryset sql-update

我想通过使用带注释的值更新queryset中的所有行.

我有一个简单的模型:

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)
Run Code Online (Sandbox Code Playgroud)

我想要解释这段代码:

for relation in Relation.objects.annotate(total_rating=Sum('sign_relations__rating')):
    relation.rating = relation.total_rating or 0
    relation.save()
Run Code Online (Sandbox Code Playgroud)

并使用以下内容更新一个SQL请求:

Relation.objects.update(rating=Sum('sign_relations__rating'))
Run Code Online (Sandbox Code Playgroud)

不起作用:

TypeError: int() argument must be a string or a number, not 'Sum'
Run Code Online (Sandbox Code Playgroud)

要么

Relation.objects.annotate(total_rating=Sum('sign_relations__rating')).update(rating=F('total_rating'))
Run Code Online (Sandbox Code Playgroud)

也不起作用:

DatabaseError: missing FROM-clause entry for table "relations_signrelation"
LINE 1: UPDATE "relations_relation" SET "rating" = SUM("relations_si...
Run Code Online (Sandbox Code Playgroud)

为此可以使用Django的ORM吗?没有关于在文档中一起使用update()annotate()的信息.

Pao*_*rre 55

对于Django 1.11+,您可以使用子查询:

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]
    )
)
Run Code Online (Sandbox Code Playgroud)

此代码生成与Tomasz Jakub Rup提出的相同的SQL代码,但没有使用RawSQL表达式(Django文档警告您使用它因为SQL注入).

更新

我发表了一篇基于这个答案的文章,有更深入的解释:

paulox.net上"使用注释和子查询更新Django查询集"

  • 哇,`更新'与`子查询`是超级酷,谢谢你指出这个模式! (3认同)
  • 我建议使用`.values_list('total_rating',flat = True)`以便直接提取值,而不要使用我最初不了解的`[:1]`。 (2认同)

Tom*_*Rup 7

UPDATE声明不支持GROUP BY.参见例如PostgreSQL Docs,SQLite Docs.

你需要这样的东西:

UPDATE relation
SET rating = (SELECT SUM(rating)
              FROM sign_relation
              WHERE relation_id = relation.id)
Run Code Online (Sandbox Code Playgroud)

相当于DjangoORM:

from django.db.models.expressions import RawSQL

Relation.objects.all(). \
    update(rating=RawSQL('SELECT SUM(rating) FROM signrelation WHERE relation_id = relation.id', []))
Run Code Online (Sandbox Code Playgroud)

要么:

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, []))
Run Code Online (Sandbox Code Playgroud)


Bar*_*tek -5

你真的不能这样做。查看代码update并仔细阅读

老实说,将这样的东西放在 Manager 定义中有什么问题吗?将您不想放入视图中的这 3 行放入管理器中,并根据需要调用该管理器。此外,你所做的“魔法”要少得多,当下一个开发人员查看你的代码时,他们将不必求助于一些WTF .. :)

另外,我很好奇,看起来你可以将SQL Join 与 UPDATE 语句一起使用,但这是一些经典的 SQL hackery .. 所以如果你愿意,你可以使用 Django 的原始 SQL 功能;)

  • “老实说,将这样的内容放入 Manager 定义中有什么问题吗?” 这可能是发送到数据库的“大量”请求。他特别要求“一个 SQL 请求”。另外,我认为他尝试的例子很容易阅读,一点也不像魔术。 (4认同)