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 DetailView - основы использования

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

Выпущены исправления Django: 2.2.7, 2.1.14 и 1.11.26

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

Как заставить request.is_ajax() работать с JS fetch()

Объект запроса Django request имеет изящный небольшой метод is_ajax(). Он позволяет определить, поступил ли запрос от JS-фреймворка (он же ajax старой школы). Хотя он отлично работает с некоторыми библиотеками JS, включая почтенный jQuery, он не будет работать с современным встроенным в JS fetch().

Практика программирования на Python 3, лекция №5

Лекции о Python 3 от Тимофея Хирьянова при поддержке Московского физико-технического института. Лекция №5.

Практика программирования на Python 3, лекция №4

Лекции о Python 3 от Тимофея Хирьянова при поддержке Московского физико-технического института. Лекция №4.

Практика программирования на Python 3, лекция №3

Лекции о Python 3 от Тимофея Хирьянова при поддержке Московского физико-технического института. Лекция №3.

Практика программирования на Python 3, лекция №2

Лекции о Python 3 от Тимофея Хирьянова при поддержке Московского физико-технического института. Лекция №2.

Практика программирования на Python 3, лекция №1

Лекции о Python 3 от Тимофея Хирьянова при поддержке Московского физико-технического института. Лекция №1.

Как загрузить файлы с Django

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

Советы по написанию миграции данных в приложении Django

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