Класс OuterRef
Класс OuterRef применяется в сочетании с классом
Subquery для построения подзапросов, которые
ссылаются на поля внешнего запроса. Это позволяет выполнять
связанные подзапросы без использования дополнительных
соединений таблиц. Класс принимает один обязательный
аргумент - имя поля, на которое нужно сослаться.
Основное преимущество OuterRef заключается в том,
что он позволяет строить динамические подзапросы, где
условия фильтрации зависят от текущей строки внешнего
запроса. Это мощный инструмент для создания сложных
агрегаций и фильтраций, которые невозможно выполнить
с помощью стандартных методов annotate и
aggregate.
Синтаксис
OuterRef(field_name)
Модели для примеров
Для демонстрации работы класса OuterRef
используем следующие модели:
from 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:
from 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}")
Результат выполнения кода:
"John Doe: 25.50"
"Jane Smith: 30.75"
"Bob Johnson: 22.33"
Пример . С фильтрацией
Используем OuterRef для фильтрации авторов,
у которых есть хотя бы одна книга с ценой выше средней:
from 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)
Результат выполнения кода:
"John Doe"
"Jane Smith"
Пример . С подсчетом связанных записей
Вычислим для каждой книги разницу между её ценой и средней ценой книг этого автора:
from 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}")
Результат выполнения кода:
"Book 1: 5.50"
"Book 2: -2.25"
"Book 3: 10.75"
"Book 4: -1.50"