Django обновляет набор запросов с аннотацией - PullRequest
30 голосов
/ 06 сентября 2010

Я хочу обновить все строки в наборе запросов, используя аннотированное значение.

У меня есть простые модели:

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...

Можно ли использовать для этого ORM Джанго? Нет информации об использовании update () и annotate () вместе в документах.

Ответы [ 6 ]

44 голосов
/ 02 мая 2018

Для 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 с аннотацией и подзапросом» on paulox.net

6 голосов
/ 28 ноября 2015

UPDATE оператор не поддерживает GROUP BY. Смотрите, например Документы PostgreSQL , Документы SQLite .

Вам нужно что-то вроде этого:

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, []))
0 голосов
/ 15 апреля 2016

Обходной путь для 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)
0 голосов
/ 05 февраля 2016

Вы можете определить свой собственный менеджер пользовательских объектов:

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 для настройки того, что возвращает этот менеджер.

0 голосов
/ 05 января 2016

Если вы хотите избежать множества обращений к базе данных, вы должны использовать transaction.atomic.

Подробнее о документации Django: https://docs.djangoproject.com/en/1.9/topics/db/transactions/#controlling-transactions-explicitly

0 голосов
/ 01 марта 2011

Вы действительно не можете этого сделать.Взгляните на код для update и выполните его через для некоторого хорошего чтения.

Честно говоря, что не так с размещением чего-то подобного в определении менеджера?Поместите те 3 строки, которые вы не хотите использовать в своем представлении, в менеджера, при необходимости вызывайте этого менеджера.Кроме того, вы делаете гораздо меньше «волшебства», и когда следующий разработчик смотрит на ваш код, им не придется прибегать к нескольким WTF ..:)

Кроме того, мне было любопытно, и это выглядитнапример, вы можете использовать SQL Join с операторами UPDATE , но это некий классический хакер SQL ... Так что, если вы так склонны, вы можете использовать для этого Djangos raw SQL;)

...