Everything you wanted to know
about the Django framework

Массовое обновление записей в Django используя аннотации и подзапросы

Предисловие

В официальной документации Django нет информации как использовать функции update() и annotate() для обновления всех строк в QuerySet используя аннотированное значение.

Сейчас мы покажем, как произвести такое обновление используя только функцию subquery() из Django ORM без использования функции extra() или SQL кода.

Модели

Для примера будем использовать код приложения блога из документации Django:

from django.db import models

class Blog(models.Model):
    name = models.CharField(max_length=100)
    rating = models.DecimalField(max_digits=3, decimal_places=2, default=5)

    def __str__(self):
        return self.name

class Entry(models.Model):
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    headline = models.CharField(max_length=255)
    rating = models.IntegerField(default=5)

    def __str__(self):
        return self.headline

Проблема

Один из путей обновления рейтинга в записях блога, основанного на подсчете среднего значения всех голосов может быть такой:

from django.db.models import Avg
from blog.models import Blog

for blog in Blog.objects.annotate(avg_rating=Avg('entry__rating')):
    blog.rating = blog.avg_rating or 0
    blog.save()

Этот код может быть очень неэффективным и медленным, если у нас будет много записей блога и их оценок, потому что Django ORM выполняет SQL запрос для каждой итерации цикла.

Чтобы избежать вышеуказанных проблем и выполнить операцию обновления одним запросом SQL, мы могли бы попробовать следующий подход:

Blog.objects.update(rating=Avg('entry__rating'))

Но этот код не работает и выдаст ошибку:

Traceback (most recent call last):
...
FieldError: Joined field references are not permitted in this query

Решение

Начиная с Django 1.11 появилась возможность использовать Django ORM с функцией subquery().

from django.db.models import Avg, OuterRef, Subquery
from blog.models import Blog, Entry

Blog.objects.update( 
    rating=Subquery( 
        Blog.objects.filter( 
            id=OuterRef('id') 
        ).annotate( 
            avg_rating=Avg('entry__rating') 
        ).values('avg_rating')[:1] 
    ) 
)

Например, в PostreSQL результат будет такой (как перевести проект Django с MySQL на PostgreSQL можно узнать в другой статье на нашем сайте):

UPDATE "blog_blog"
SET "rating" = (
   SELECT AVG(U1."rating") AS "avg_rating"
   FROM "blog_blog" U0
   LEFT OUTER JOIN "blog_entry" U1 ON (U0."id" = U1."blog_id")
   WHERE U0."id" = ("blog_blog"."id")
   GROUP BY U0."id"
   LIMIT 1
)

 

Перевод статьи https://www.paulox.net/2018/10/01/updating-a-django-queryset-with-annotation-and-subquery/

Поделитесь с другими:

Представления-классы
(Class-Based Views)

Детальное описание и структура классов Django.

Усовершенствованное отображение форм с помощью Django Crispy Forms

В этом уроке мы собираемся изучить некоторые функции Django Crispy Forms для обработки расширенных/пользовательских форм с использованием Bootstrap 4.

Запрос по сумме из связанной модели

Понадобилось тут найти несоответствие суммы платежа Payment с суммой связанных ним элементов PaymentItem. Решается это простой аннотацией.

Стала доступна версия Django 3.0 alpha 1

Стала доступна версия Django 3.0 alpha 1. Она представляет собой первый этап цикла выпуска 3.0 и дает вам возможность опробовать изменения, появившиеся в Django 3.0.

Исправления ошибок Django 2.2.5, 2.1.12 и 1.11.24

Сегодня команда разработчиков Django выпустила версии с исправлениями 2.2.5, 2.1.12 и 1.11.24. Пакет релиза и контрольные суммы доступны на странице загрузок, а также из индекса пакетов Python. Идентификатор ключа PGP, использованный в этом выпуске: Mariusz Felisiak: 2EF56372BA48CD1B.

Создание большой XML-карты сайта для Django

Предположим, что у вас так много страниц (тысячи), что вы не можете просто создать один файл /sitemap.xml, в котором перечислены все URL-адреса (он же <loc>). Поэтому вам нужно создать /sitemaps.xml, который указывает на другие файлы карты сайта. А если адресов в каждом тысячи, то нужно сжать эти файлы.

Добавление хранилища Amazon S3 в проект Джанго

В этом руководстве вы узнаете, как использовать сервис Amazon S3 для обработки статических ресурсов и загруженных пользователем файлов, то есть медиа-ресурсов.

Пользовательская модель User

Каждый новый проект Django должен использовать пользовательскую модель User. Официальная документация Django гласит, что это «настоятельно рекомендуется», но я сделаю еще один шаг и без колебаний скажу: вы просто с ума сошли, если не использовали пользовательскую модель раньше.

Создание погодного приложения на Python

Видео, в котором с нуля создаётся полноценное приложение на Python и Django, позволяющие получать информацию про погодные условия в различных городах мира.

Django 2.2.1 - исправления ошибок

Сегодня выпустили версию 2.2.1 с исправлениями ошибок.

Исправление ошибок, Django 2.1.8

Выпущена новая версия Django 2.1.8, в которой исправлена ошибка в админке, допущенная в версии 2.1.7: запрещено редактировать inline-элементы для ManyToManyField, если у пользователя есть только разрешение на просмотр.