在order_by之后,如何获取结果在列表中的位置?

13

我想找到一种有效的方法,在数据库中查找与其得分相关的对象排名。我的朴素解决方案如下:

rank = 0
for q in Model.objects.all().order_by('score'):
  if q.name == 'searching_for_this'
    return rank
  rank += 1

可以使用 order_by 来让数据库进行过滤:

Model.objects.all().order_by('score').filter(name='searching_for_this')

但是,在筛选之后似乎找不到 order_by 步骤的索引。

有更好的方法吗?(使用python / django和/或原始SQL。)

我接下来的想法是在插入时预先计算排名,但那看起来很混乱。


  1. select score as retrieved_score where name='searching_for_this'
  2. select count(*) where score <= retrieved_score -> 排名
- jfs
4个回答

17

我认为你不能使用Django ORM在一个数据库查询中完成这个操作。但是如果不会打扰你的话,我会在一个模型上创建一个自定义方法:

from django.db.models import Count

class Model(models.Model):
    score = models.IntegerField()
    ...

    def ranking(self):
        count = Model.objects.filter(score__lt=self.score).count()
        return count + 1

您可以将“ranking”视为普通字段在任何地方使用:

print Model.objects.get(pk=1).ranking

编辑:这个答案是2010年的内容。现在我会建议使用Carl的解决方案


请注意,在您提供的示例中,顺序是升序的(换句话说,“得分越低,排名越高”),因此我按照这个顺序进行了操作。如果更高的分数实际上应该增加排名,则应将“score__lt”更改为“score__gt”。谢谢。 - Ludwik Trammer
3
这可能会非常耗费计算资源,因为它将对 Model.objects.all() 中的每一行运行排名聚合查询。 - mehmet
@mehmet 当然可以。这就是我在这篇文章的第一段中试图表达的意思。当时(6年前),使用Django ORM没有更有效的方法来完成这个任务。现在,可能有一个仅使用PostgreSQL窗口函数和自定义注释函数的解决方案。等我有时间了,我会研究一下这个问题。 - Ludwik Trammer

13

使用 Django 2.0 中的新窗口函数,您可以像这样编写代码...

from django.db.models import Sum, F
from django.db.models.expressions import Window
from django.db.models.functions import Rank

Model.objects.filter(name='searching_for_this').annotate(
            rank=Window(
                expression=Rank(),
                order_by=F('score').desc()
            ),
        )

2
这很棒,我唯一的问题是当我在查询集之后进行更多过滤时,它没有保留排名。就像我获取所有结果并对它们进行排名,然后按用户名进行过滤,结果是根据较小的查询集而不是整个查询集进行排名的。 - Tusk
我解决问题的方式可能不是最好的,我创建了一个辅助字典来存储全局查询集中的用户ID和等级,并且为了显示实际等级,我使用了这个辅助字典而不是将等级添加到过滤后的查询集中。 - Tusk
它能在MySQL 5.7的Aurora版本上运行吗?有什么想法吗? - Mayank Pratap Singh

3

使用类似以下的内容:

obj = Model.objects.get(name='searching_for_this')
rank = Model.objects.filter(score__gt=obj.score).count()

您可以预先计算排名并将其保存到模型中,如果它们经常被使用并影响性能。

2
在标准兼容的数据库引擎(PostgreSql、SQL Server、Oracle、DB2等)中使用"原始SQL",你可以直接使用SQL标准的RANK函数。但在流行但非标准的引擎(MySql和Sqlite)中不支持该函数,也许正因为如此,Django没有将这个功能呈现给应用程序。请注意保留HTML标签。

啊,我在部署到PGSQL之前正在Sqlite上进行测试。 - Bob Bob
2
@BobBob,如果你想的话,你可以运行一个小的本地PostgreSQL实例进行测试--它既不难也不繁琐。 - Alex Martelli

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