【问题标题】:Django reverse lookup to get latestDjango反向查找以获取最新信息
【发布时间】:2020-10-29 06:42:26
【问题描述】:

我的模型结构如下,

ACTIVE_STATUS = ['waiting', 'loading', 'starting', 'running', 'stopping']
INACTIVE_STATUS = ['stopped', 'finished', 'failed', 'lost']
ALL_STATUS = ACTIVE_STATUS + INACTIVE_STATUS


class Task(models.Model):
    name = models.CharField(max_length=20)


class Job(models.Model):
    task = models.ForeignKey(Task, related_name='jobs')
    timestamp = models.DateTimeField(auto_now_add=True)
    status = models.CharField(choices=zip(ALL_STATUS, ALL_STATUS), max_length=20)

如何将“最新时间戳及其状态”注释到任务查询集中?

我已经设法获得了最新的时间戳,

Task.objects.annotate(latest_ts=models.Max(models.F('job__timestamp')))

那么,怎样才能得到对应的status呢?

更新 1

这个查询的最大目的是对Task查询集进行排序

  1. 零作业(例如Task.objects.filter(job__isnull=True)
  2. latest_job=='正在运行'

更新 2

用于获取排序查询集的TaskManager类

class TaskManager(models.Manager):

    def get_queryset(self):
        qs = super().get_queryset()
        latest_job = models.Max(models.F('job__timestamp'))

        latest_status = models.Subquery(
            Job.objects.filter(
                task_id=models.OuterRef('pk')
            ).values('status').order_by('-timestamp')[:1]
        )

        qs_order = models.Case(
            models.When(job__isnull=True, then=models.Value(2)),
            models.When(latest_status='running', then=models.Value(1)),
            default=models.Value(0),
            output_field=models.IntegerField()
        )

        return qs.annotate(latest_job=latest_job, latest_status=latest_status, qs_order=qs_order).order_by('-qs_order')

【问题讨论】:

  • 你使用什么数据库?
  • 我正在使用 MySQL
  • @这是否意味着您想要没有工作的项目 in 或不在查询集中?
  • @WillemVanOnsem 想要 in 查询集中没有作业的项目
  • 如果你使用models.When(job__isnull=True, then=models.Value(2)),,那么这将导致一个JOIN,所以同样的Task会被重复很多次,因为有Jobs存在于那个Task

标签: python django django-orm


【解决方案1】:

您可以使用Subquery expression [Django-doc]

from django.db.models import OuterRef, Subquery

Task.objects.annotate(
    latest_status=Subquery(
        Job.objects.filter(
            task_id=OuterRef('pk')
        ).values('status').order_by('-timestamp')[:1]
    )
)

基于此,您或许还可以过滤最新状态:

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

Task.objects.annotate(
    latest_status=Subquery(
        Job.objects.filter(
            task_id=OuterRef('pk')
        ).values('status').order_by('-timestamp')[:1]
    )
).filter(
    Q(jobs=None) | Q(latest_status='running')
)

或者我们可以通过Job等的存在来订购:

from django.db.models import BooleanField, Exists, ExpressionWrapper, Max, Q
from django.db.models import OuterRef, Subquery

Task.objects.annotate(
    latest_status=Subquery(
        Job.objects.filter(
            task_id=OuterRef('pk')
        ).values('status').order_by('-timestamp')[:1]
    ),
    latest_job=Max('jobs__timestamp')
).order_by(
    Exists(Job.objects.filter(task_id=OuterRef('pk'))).asc(),
    ExpressionWrapper(Q(latest_status='running'), output_field=BooleanField()).asc(),
    'pk'
)

最终在主键上进行过滤以使排序确定性可能是个好主意。

【讨论】:

  • 谢谢,看来它的工作。不幸的是,我忘记将我的最大场景放在 OP 中。你介意再检查一遍OP吗?
  • @ArakkalAbu:你能看看过滤是否解决了问题吗?
  • 我不这么认为,因为我正在将此 QuerySet 推送到 DRF ListAPI(启用分页)
  • 我没有收到任何错误,但是,这是否包括 latest_status != 'running' ? (我认为,No)除此之外,这是一个 *order by 问题,对吧?
  • @ArakkalAbu:通常它不应该包括Tasks,其中最新状态是running no。例如,您可以添加.order_by('pk') 以确保如果作业同时没有更改,则下一页将继续上一页结束的位置。
【解决方案2】:

Willem 的 answer 看起来很有希望,但是,我设法通过注释作业数来获得排序。

这是最后的模型管理器,

class TaskManager(models.Manager):

    def get_queryset(self):
        qs = super().get_queryset()
        latest_job = models.Max(models.F('jobs__timestamp'))

        latest_status = models.Subquery(
            Job.objects.filter(
                task_id=models.OuterRef('pk')
            ).values('status').order_by('-timestamp')[:1]
        )
        job_count = models.Count('jobs')

        qs_order = models.Case(
            models.When(job_count=0, then=models.Value(2)),
            models.When(latest_status='running', then=models.Value(1)),
            default=models.Value(0),
            output_field=models.IntegerField()
        )

        return qs.annotate(job_count=job_count,
                           latest_job=latest_job,
                           latest_status=latest_status,
                           qs_order=qs_order
                           ).order_by('-qs_order', '-pk')

结果截图

【讨论】:

    猜你喜欢
    • 2013-11-24
    • 1970-01-01
    • 2011-10-01
    • 2014-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-07
    相关资源
    最近更新 更多