You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django高效关联查询过滤特定行:获取作者最新书籍信息

问题描述

假设有以下Django模型:

from django.db import models

class Author(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)

class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=128)
    date = models.DateTimeField()
    author = models.ForeignKey(Author, related_name='books', on_delete=models.CASCADE)

需要实现一个高效的查询集,获取所有作者,并为每个作者标注其最新书籍的名称和日期。要求尽可能避免为每个所需字段创建单独的子查询(实际场景中需要获取更多书籍字段),优先通用过滤方案,针对最新日期的特定方案也可接受。


已尝试的方案

Subquery方案

可以用子查询实现,但子查询只能返回单列,因此每个书籍字段都需要单独创建子查询,效率极低:

books = Book.objects.filter(author=OuterRef('pk')).order_by('-date')
qs = Author.objects.all().annotate(
    latest_book_name=Subquery(books.values('name')[:1]),
    latest_book_date=Subquery(books.values('date')[:1]),
)

Prefetch方案

通过prefetch_related获取作者关联的所有书籍,并用Max标注最新书籍日期,但无法同时获取对应书籍的名称:

qs = Author.objects.all().prefetch_related(Prefetch("books"))
qs = qs.annotate(latest_book_date=Max("books__date"))

Django中没有直接的Argmax运算符,无法通过此方法关联到对应书籍的其他字段。

first/latest方法

first()或latest()返回单个对象,无法作为标注值添加到查询集中,不适用。

匹配最大ID方案

部分场景通过标注Max("books__id")并匹配书籍ID来筛选最新书籍,但本场景中书籍并非按时间顺序添加(实际甚至使用UUID作为主键),此方案不适用。


高效解决方案

方案1:使用Window函数(推荐,Django 2.0+)

利用Window和Rank函数为每个作者的书籍按日期倒序排名,筛选出排名为1的最新书籍,再与作者表关联,一次查询即可获取所有所需字段:

from django.db.models import Window, F
from django.db.models.functions import Rank

# 为书籍按作者分组,按date倒序排名
ranked_books = Book.objects.annotate(
    rank=Window(
        expression=Rank(),
        partition_by=F('author_id'),
        order_by=F('date').desc()
    )
).filter(rank=1)

# 关联作者和最新书籍,获取所需字段
qs = Author.objects.annotate(
    latest_book_name=F('books__name'),
    latest_book_date=F('books__date')
).filter(books__in=ranked_books).distinct()

此方案仅需少量数据库查询,且无需为每个字段单独创建子查询,适合需要获取多个书籍字段的场景。

方案2:子查询关联最新日期的书籍

先通过子查询获取每个作者的最新书籍日期,再关联书籍表获取对应字段,避免多字段子查询:

from django.db.models import Subquery, OuterRef, Max

# 获取每个作者的最新书籍日期
latest_dates = Author.objects.annotate(
    latest_date=Max('books__date')
).values('pk', 'latest_date')

# 筛选出每个作者最新日期对应的书籍
latest_books = Book.objects.filter(
    author_id=OuterRef('pk'),
    date=OuterRef('latest_date')
)

# 标注最新书籍的字段
qs = Author.objects.annotate(
    latest_book_name=Subquery(latest_books.values('name')[:1]),
    latest_book_date=Subquery(latest_books.values('date')[:1])
)

此方案仅需两次子查询(而非每个字段一次),效率优于原始的多字段子查询方案,适合无法使用Window函数的低版本Django。


内容的提问来源于stack exchange,提问作者Jean Bouvattier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 03:41:02