Класс OuterRef

Класс OuterRef применяется в сочетании с классом Subquery для построения подзапросов, которые ссылаются на поля внешнего запроса. Это позволяет выполнять связанные подзапросы без использования дополнительных соединений таблиц. Класс принимает один обязательный аргумент - имя поля, на которое нужно сослаться.

Основное преимущество OuterRef заключается в том, что он позволяет строить динамические подзапросы, где условия фильтрации зависят от текущей строки внешнего запроса. Это мощный инструмент для создания сложных агрегаций и фильтраций, которые невозможно выполнить с помощью стандартных методов annotate и aggregate.

Синтаксис

pythonOuterRef(field_name)

Модели для примеров

Для демонстрации работы класса OuterRef используем следующие модели:

pythonfrom django.db import models

class Author(models.Model):
  name = models.CharField(max_length=100)
  age = models.IntegerField()
  
  def __str__(self):
    return self.name

class Book(models.Model):
  title = models.CharField(max_length=200)
  author = models.ForeignKey(Author, on_delete=models.CASCADE)
  price = models.DecimalField(max_digits=10, decimal_places=2)
  pages = models.IntegerField()
  published = models.BooleanField(default=True)
  
  def __str__(self):
    return self.title

Пример . С аннотацией

Давайте аннотируем каждого автора средней ценой его книг с помощью подзапроса с OuterRef:

pythonfrom django.db.models import OuterRef, Subquery, Avg
from myapp.models import Author, Book

# Подзапрос для вычисления средней цены книг каждого автора
avg_price_subquery = Book.objects.filter(
  author=OuterRef('pk')
).values('author').annotate(
  avg_price=Avg('price')
).values('avg_price')

authors = Author.objects.annotate(
  avg_book_price=Subquery(avg_price_subquery)
)

for author in authors:
  print(f"{author.name}: {author.avg_book_price}")

Результат выполнения кода:

python"John Doe: 25.50"
"Jane Smith: 30.75"
"Bob Johnson: 22.33"

Пример . С фильтрацией

Используем OuterRef для фильтрации авторов, у которых есть хотя бы одна книга с ценой выше средней:

pythonfrom django.db.models import OuterRef, Subquery, Exists, Avg
from myapp.models import Author, Book

# Подзапрос для проверки наличия книг с ценой выше средней
expensive_books_subquery = Book.objects.filter(
  author=OuterRef('pk'),
  price__gt=Book.objects.filter(
    author=OuterRef('pk')
  ).values('author').annotate(
    avg_price=Avg('price')
  ).values('avg_price')
)

authors = Author.objects.filter(
  Exists(expensive_books_subquery)
)

for author in authors:
  print(author.name)

Результат выполнения кода:

python"John Doe"
"Jane Smith"

Пример . С подсчетом связанных записей

Вычислим для каждой книги разницу между её ценой и средней ценой книг этого автора:

pythonfrom django.db.models import OuterRef, Subquery, Avg, F
from myapp.models import Book

# Подзапрос для получения средней цены книг автора
author_avg_price = Book.objects.filter(
  author=OuterRef('author')
).values('author').annotate(
  avg_price=Avg('price')
).values('avg_price')

books = Book.objects.annotate(
  price_difference=F('price') - Subquery(author_avg_price)
)

for book in books:
  print(f"{book.title}: {book.price_difference}")

Результат выполнения кода:

python"Book 1: 5.50"
"Book 2: -2.25"
"Book 3: 10.75"
"Book 4: -1.50"

Смотрите также

  • класс Subquery,
    который используется для создания подзапросов
  • класс Exists,
    который проверяет существование записей в подзапросе
  • класс F,
    который позволяет ссылаться на поля модели в запросах
  • метод annotate,
    который добавляет вычисляемые поля к QuerySet