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

如何用Django ORM为每个机器关联其最新的Mission外键对象

问题:Django ORM 查询机器并关联其最新任务

我在编写查询语句时遇到了困难,模型定义如下:

Mission 模型

class Mission(models.Model):
    machine = models.ForeignKey(Machine, on_delete=models.CASCADE, related_name="missions")
    start_dt = models.DateTimeField(null=False)
    longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True)
    latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True)

    @cached_property
    def timezone(self) -> datetime.tzinfo:
        timezone_str = get_tz(float(self.longitude), float(self.latitude))
        return pytz.timezone(timezone_str)

Machine 模型

class Machine(models.Model):
    name = models.CharField(max_length=12, null=False, unique=True)
    created_at = models.DateTimeField(_("created at"), auto_now_add=True)
    updated_at = models.DateTimeField(_("updated at"), auto_now=True)

需求是查询所有机器,为每个机器添加last_mission字段存储其最新任务,从而可以通过machine.last_mission.timezone访问任务属性。

我尝试了一段PostgreSQL查询,但无法生成last_mission键,也无法直接使用模型属性,不符合需求:

select * from machines_machine machine, missions_mission mission where (mission.machine_id, mission.start_dt)  in (select mission.machine_id as machine_id, Max(mission.start_dt) as start_dt  from missions_mission mission group by mission.machine_id  order by mission.machine_id)

解决方案

可以通过Django ORM的Subquery和OuterRef实现需求,直接为每个Machine实例关联最新的Mission对象,以下是两种可行方案:

方法一:使用Subquery关联单个最新任务对象

  1. 导入所需ORM工具:
from django.db.models import Subquery, OuterRef
  1. 定义子查询获取每个机器的最新任务:
# 按start_dt倒序排序,取每个机器的第一条任务(即最新任务)
latest_mission_subquery = Mission.objects.filter(
    machine=OuterRef('pk')
).order_by('-start_dt').values('pk')[:1]
  1. 查询所有机器并关联最新任务:
# 为每个机器添加last_mission_id字段,再通过select_related关联完整的Mission对象
machines = Machine.objects.annotate(
    last_mission_id=Subquery(latest_mission_subquery)
).select_related('last_mission')

查询完成后,每个machine实例会直接拥有last_mission属性,可直接访问模型属性:

for machine in machines:
    if machine.last_mission:
        print(machine.last_mission.timezone)

方法二:使用Prefetch预取最新任务集合

如果需要更灵活的预取控制,可通过Prefetch指定预取每个机器的最新任务:

  1. 导入所需工具:
from django.db.models import Prefetch, Max
  1. 筛选出所有机器的最新任务集合:
# 先获取每个机器的最新start_dt
max_start_dts = Mission.objects.values('machine').annotate(max_dt=Max('start_dt')).values('max_dt')
# 筛选出所有符合条件的最新任务
latest_missions = Mission.objects.filter(start_dt__in=max_start_dts)
  1. 查询机器时预取最新任务到指定属性:
machines = Machine.objects.prefetch_related(
    Prefetch(
        'missions',
        queryset=latest_missions,
        to_attr='last_mission'
    )
)

此时machine.last_mission是包含最新任务的列表(通常仅一个元素),访问方式如下:

for machine in machines:
    if machine.last_mission:
        print(machine.last_mission[0].timezone)

方案说明

  • 方法一通过annotate+select_related直接关联单个Mission对象,访问更直接,适合需要直接调用属性的场景。
  • 方法二通过Prefetch预取,适合批量处理或需要额外过滤条件的场景。
  • 两种方式都能完整保留Mission模型的所有属性,包括自定义的timezone缓存属性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 17:02:44