Django中如何用外键去重并优先按时间戳排序查询集?
问题描述
在Django项目中使用PostgreSQL,需要根据客户full_name搜索工单,获取每个customer_id唯一的结果,并且按Ticket模型的card_timestamp字段降序排序(最新工单排在前面)。
现有模型定义:
class Ticket(BaseTimeClass, BaseDeletedClass): customer=models.ForeignKey(Customer,on_delete=models.CASCADE, related_name="generated_tickets") is_replied = models.BooleanField(default=False) is_resolved = models.BooleanField(default=False) card_timestamp = models.DateTimeField(blank=True, null=True) class Customer(BaseDeletedClass): created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True, db_index=True) modified_at = models.DateTimeField(auto_now=True, blank=True, null=True) first_name = models.CharField(max_length=64, default="") last_name = models.CharField(max_length=64, default="") full_name = models.CharField(max_length=256, default="")
尝试的查询语句:
Ticket.objects.filter(Q(customer__full_name__icontains="shuvo")) .select_related("customer") .order_by("customer_id", "-card_timestamp") .distinct("customer_id")
当前问题:该查询先按customer_id排序,再按card_timestamp排序,导致最终结果顺序不符合预期。例如三个客户ID为1870、1120、3210,对应最新工单的card_timestamp分别是2023-01-13、2022-12-28、2022-12-14,期望返回顺序是1870 → 1120 → 3210,但实际返回顺序是3210 → 1870 → 1120。
解决方案
问题根源是PostgreSQL中distinct("customer_id")要求指定的字段必须是order_by的首个字段,导致结果被customer_id主导排序,无法直接按card_timestamp整体排序。以下两种方式可解决需求:
方式一:子查询+注解
先为每个客户筛选出最新工单的时间,再以此为依据排序结果:
from django.db.models import Subquery, OuterRef # 子查询:获取每个客户最新工单的card_timestamp latest_ticket_subquery = Ticket.objects.filter( customer_id=OuterRef('customer_id') ).order_by('-card_timestamp').values('card_timestamp')[:1] # 主查询流程 result = Ticket.objects.filter( customer__full_name__icontains="shuvo" ).select_related("customer") # 为工单添加所属客户的最新工单时间注解 .annotate(latest_card_timestamp=Subquery(latest_ticket_subquery)) # 先按customer_id去重,保留每个客户的最新工单 .order_by('customer_id', '-card_timestamp').distinct('customer_id') # 最终按客户的最新工单时间降序排序 .order_by('-latest_card_timestamp')
方式二:Window函数
通过窗口函数为每个客户的工单编号,直接筛选最新工单后排序:
from django.db.models import Window, F from django.db.models.functions import RowNumber result = Ticket.objects.filter( customer__full_name__icontains="shuvo" ).select_related("customer") # 按customer_id分组,组内按card_timestamp降序编号,最新工单编号为1 .annotate( row_num=Window( expression=RowNumber(), partition_by=F('customer_id'), order_by=F('card_timestamp').desc() ) ) # 筛选每个客户的最新工单 .filter(row_num=1) # 按工单时间降序排序最终结果 .order_by('-card_timestamp')
内容的提问来源于stack exchange,提问作者Raisul
相关产品推荐
相关产品推荐

