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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 23:55:31