【问题标题】:How do I sort a Django QuerySet by an external value?如何按外部值对 Django QuerySet 进行排序?
【发布时间】:2012-10-23 22:05:11
【问题描述】:

我有一个由 (id, rank) 对组成的字典。我想对 id 执行 Django 查询,以便结果查询集按排名(降序)排序。

获取查询集很简单:

rankings = {...}
result = MyModel.objects.filter(id__in=rankings.keys())

似乎答案应该涉及某种注释,我可以将其用作 order_by 的一部分,但我不知道如何到达那里。

编辑:我没有提到我需要将结果作为一个 QuerySet,因为这是一个美味的 API 管道的一部分。

【问题讨论】:

  • 我认为你做不到。将这些值存储在字段中怎么样?
  • 每次查询的排名都会发生变化。该应用程序正在查看一组学校,并根据表达的偏好为用户对它们进行排名。
  • 我的建议是你添加一个表来存储排名,然后进行连接。这将允许您基于连接表/字段的值执行 MyModel.objects.order_by(...) 之类的操作。

标签: django django-queryset


【解决方案1】:

我对 Django > 1.10 和 PostgreSQL > 9.5 的解决方案

from django.db.models import Func, Value, IntegerField, CharField
from django.contrib.postgres.fields import ArrayField


class ArrayPosition(Func):
    function = 'array_position'

    def __init__(self, items, *expressions, **extra):
        if isinstance(items[0], int):
            base_field = IntegerField()
        else:
            base_field = CharField(max_length=max(len(i) for i in items))
        first_arg = Value(list(items), output_field=ArrayField(base_field))
        expressions = (first_arg, ) + expressions
        super().__init__(*expressions, **extra)


pk_list = [234,12,23]
queryset = SomeModel.objects.filter(pk__in=pk_list, ...)\
    .annotate(ordering=ArrayPosition(pk_list, F('pk'), output_field=IntegerField()))\
    .order_by('ordering')

【讨论】:

【解决方案2】:

这样的?

rankings = { 1 : 2, 2: 1, ... } # i.e. { 'id' : 'ranking', ... }
objects = list(MyModel.objects.filter(id__in=rankings.keys()))
objects.sort(key=lambda obj: rankings[obj.id])

【讨论】:

  • 有趣的解决方案。这种东西有多贵?
  • 就是这个想法,但是 QuerySet 上没有“排序”方法,所以我认为这段代码行不通。
  • 我已经编辑了代码 - 您可以将 QS 转换为 List 但这意味着立即评估 QS。我不确定排序的效率,但我不确定是否有替代解决方案(据我所知,肯定不涉及Queryset API)
  • 我认为这可行,但如果您使用 Paginator,可能会出现问题。
  • 我没有提到(但已经编辑了原始问题以反映)我需要将结果作为 QuerySet。
【解决方案3】:

我能想出解决这个问题的唯一方法是创建一个与主要模型相关的新排名模型。在每个查询中,我将排名项目插入该模型,然后能够通过关系执行 order_by。 (使用注释将排名添加到学校记录中。)

class UserSchoolRanking(models.Model):
    user = models.ForeignKey(User)
    school = models.ForeignKey(School)
    rank = models.IntegerField()

bulk_user_school_rank = [UserSchoolRank(user=user, school_id=k, rank=v)
                                         for k, v in rankings.iteritems()]
UserSchoolRank.objects.bulk_create(bulk_user_school_rank)

schools = School.objects.filter(userschoolrank__user=user)\
                .annotate(rank=Min('userschoolrank__rank'))\
                .order_by('-userschoolrank__rank')

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-05-26
    • 1970-01-01
    • 1970-01-01
    • 2011-05-09
    • 2015-06-21
    • 2022-11-30
    • 1970-01-01
    相关资源
    最近更新 更多