Django updatesetset с аннотацией
Я хочу обновить все строки в наборе запросов, используя аннотированное значение.
У меня есть простые модели:
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() вместе в документах.
Ответы
Ответ 1
Для 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]
)
)
Этот код создает тот же код SQL, предложенный Tomasz Jakub Rup, но без использования выражения RawSQL (документация Django предупреждает вас об использовании его, поскольку SQL-инъекция).
Обновить
Я опубликовал статью, основанную на этом ответе, с более подробными пояснениями:
"Обновление набора запросов Django с аннотацией и подзапросом" на paulox.net
Ответ 2
Оператор UPDATE
не поддерживает GROUP BY
. См. Документы PostgreSQL, 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, []))
Ответ 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)
Ответ 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
Вы действительно не можете этого сделать. Посмотрите код для update
и следовать за ним для некоторого тонкого чтения.
Честно говоря, что случилось с размещением чего-то подобного в определении менеджера? Поместите эти 3 строки, которые вы не хотите отображать в своем представлении, в менеджер, при необходимости вызовите этого менеджера. Кроме того, вы делаете гораздо меньше "магии", и когда следующий разработчик смотрит на ваш код, им не придется прибегать к нескольким WTF..:)
Кроме того, мне было любопытно, и похоже, вы можете использовать SQL Присоединиться к операторам UPDATE, но это классический хакер SQL. Поэтому, если вы 'так склонны, вы можете использовать Djangos raw SQL для этого;)
Ответ 6
Если вы хотите избежать многих вызовов в базе данных, вы должны использовать transaction.atomic
.
Подробнее о документации Django: https://docs.djangoproject.com/en/1.9/topics/db/transactions/#controlling-transactions-explicitly