Django按特定顺序排序

22
在Django ORM中是否可能复制这种特定的SQL排序方式:
order by

(case

    when id = 5 then 1

    when id = 2 then 2

    when id = 3 then 3

    when id = 1 then 4

    when id = 4 then 5

end) asc

?

4个回答

57

自从Django 1.8版本以后,您可以使用条件表达式,所以不再需要使用extra了。

from django.db.models import Case, When, Value, IntegerField

SomeModel.objects.annotate(
    custom_order=Case(
        When(id=5, then=Value(1)),
        When(id=2, then=Value(2)),
        When(id=3, then=Value(3)),
        When(id=1, then=Value(4)),
        When(id=4, then=Value(5)),
        output_field=IntegerField(),
    )
).order_by('custom_order')

1
使用annotate并进行order_by,与直接在order_by中设置Case有什么区别? - gdvalderrama
@guival 我也遇到了同样的问题,你有答案了吗? - Bialomazur
@Bialomazur 我仍然不确定它们之间的区别,所以我决定将Case直接放在order_by中,而不进行annotate操作,这样就可以了。 - gdvalderrama
1
我发现这种方法非常有用,尤其是在将 custom_order 与模型中其他字段的附加排序组合时,例如 .order_by('custom_order', '-created_at') - msonsona
在这里帮助我的部分是 output_field。Django 不断地改变推断的工作方式,因此最好始终包括一个输出字段。 - munsu

26

可以。自Django 1.8版本以来,您可以按照以下方式进行操作:

from django.db.models import Case, When

ids = [5, 2, 3, 1, 4]
order = Case(*[When(id=id, then=pos) for pos, id in enumerate(ids)])
queryset = MyModel.objects.filter(id__in=ids).order_by(order)

我简直不敢相信那个能够运行。我不知道是怎么做到的,但这就是编程。 - Kurt
@Hiroki 太棒了! - good_evening

5
你可以使用extra() 或者更简单的raw()来实现,但是在更复杂的情况下可能无法正常工作。
qs.extra(select={'o':'(case when id=5 then 1 when id=2 then 2 when id=3 then 3 when id=1 then 4 when id=4 then 5 end)', order_by='o'}

YourModel.raw('select ... order by (case ...)')

对于您的代码,条件设置非常有限,您可以轻松地在Python中进行排序。


0
我回答这个古老的问题是为了分享Django的进化。
自从Django 3.2版本以来,有一个非常适合这种情况的alias()
与annotate()相同,但不是在QuerySet中注释对象,而是保存表达式以便以后在其他QuerySet方法中重用。当表达式本身的结果不需要,但用于过滤、排序或作为复杂表达式的一部分时,这非常有用。不选择未使用的值可以减少数据库中的冗余工作,从而提高性能。
from django.db.models import Case, Value, When

class MyObject(models.Model):
    priority = models.CharField(max_length=10)

priority_order = Case(
    When(priority='high', then=Value(1)),
    When(priority='medium', then=Value(2)),
    When(priority='low', then=Value(3)),
)
MyObject.objects.alias(priority_order=priority_order).order_by("priority_order")

根据Django文档,看起来alias()annotate()或在order_by()中直接使用表达式更好:

filter()和order_by()可以直接使用表达式,但是表达式的构建和使用通常不会发生在同一个地方(例如,QuerySet方法在视图中创建表达式,以供以后使用)。alias()允许逐步构建复杂的表达式,可能跨越多个方法和模块,通过别名引用表达式的部分,并且仅在最终结果中使用annotate()。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接