Django如何通过annotate/prefetch查询账户及最新付款信息?
解决Django账户与最新付款记录关联查询的问题
嘿,这个需求我之前也碰到过,用Django的Subquery和OuterRef就能完美搞定,而且只需要一次数据库查询就够!先帮你理清楚整个流程:
先明确模型结构(假设你的模型是这样的)
首先得确认下你的模型关系,我先按常规场景假设两个核心模型:
from django.db import models class Account(models.Model): name = models.CharField(max_length=100) # 账户名称,比如"餐饮"、"饮品" class Payment(models.Model): account = models.ForeignKey(Account, on_delete=models.CASCADE, related_name="payments") payer = models.CharField(max_length=100) # 付款人姓名 amount = models.DecimalField(max_digits=10, decimal_places=2) # 付款金额 date = models.DateField() # 付款日期
方案一:用Subquery实现单次查询(推荐)
这个方法会把最新付款的信息直接标注到每个Account对象上,全程只触发一次数据库查询,性能最优:
from django.db.models import OuterRef, Subquery # 第一步:构建子查询,获取每个账户的最新付款记录(按付款日期倒序取第一条) latest_payment = Payment.objects.filter( account=OuterRef('pk') # OuterRef引用主查询中Account的主键 ).order_by('-date')[:1] # 第二步:查询所有账户,同时标注最新付款的关键信息 accounts = Account.objects.annotate( latest_payer=Subquery(latest_payment.values('payer')), latest_amount=Subquery(latest_payment.values('amount')), latest_date=Subquery(latest_payment.values('date')) )
代码解释
OuterRef用来关联主查询的Account对象和子查询的Payment对象,确保子查询只针对当前Account的付款记录Subquery会把每个账户的最新付款字段值注入到主查询的Account结果中- 如果某个账户还没有任何付款记录,对应的
latest_payer、latest_amount、latest_date会是None,你可以在模板里用default过滤器处理这种情况
模板展示(对应你要的表格)
拿到查询结果后,直接在模板里渲染成表格就行:
<table> <thead> <tr> <th>账户</th> <th>最后付款人</th> <th>最后付款金额</th> <th>最后付款日期</th> </tr> </thead> <tbody> {% for account in accounts %} <tr> <td>{{ account.name }}</td> <td>{{ account.latest_payer|default:"无付款记录" }}</td> <td>{{ account.latest_amount|default:"0.00" }}</td> <td>{{ account.latest_date|date:"Y年n月j日"|default:"无" }}</td> </tr> {% endfor %} </tbody> </table>
方案二:用Prefetch实现两次查询(可读性更好)
如果你觉得Subquery的写法有点繁琐,也可以用Prefetch来预加载每个账户的最新付款记录,这个方案会触发两次查询(一次查账户,一次查过滤后的付款记录),开销也很小:
from django.db.models import Prefetch # 定义Prefetch对象,只预加载每个账户的最新付款记录 latest_payments_prefetch = Prefetch( 'payments', # 对应Account模型中Payment的related_name queryset=Payment.objects.order_by('-date')[:1], # 取每个账户最新的一条付款 to_attr='latest_payment' # 把结果存到Account对象的latest_payment属性里 ) # 查询所有账户并预加载最新付款记录 accounts = Account.objects.prefetch_related(latest_payments_prefetch)
模板使用方式
这种方式下,最新付款记录是一个列表(因为Prefetch返回的是查询集),所以要取第一个元素:
<td>{{ account.latest_payment.0.payer|default:"无付款记录" }}</td> <td>{{ account.latest_payment.0.amount|default:"0.00" }}</td> <td>{{ account.latest_payment.0.date|date:"Y年n月j日"|default:"无" }}</td>
总结
- 优先选方案一,单次查询性能更优,适合数据量较大的场景
- 方案二的代码可读性更强,适合数据量不大的场景,两次查询的开销几乎可以忽略
内容的提问来源于stack exchange,提问作者Kritz
相关产品推荐
相关产品推荐

