【问题标题】:django query, distinct on a foreign key and order by another attributedjango查询,在外键上不同并按另一个属性排序
【发布时间】:2014-03-31 03:42:05
【问题描述】:

我陷入了 django 的查询中。 我有两个模型,

Class Course(models.Model):
    provider = ForeignKey(Provider)
    rating = FloatField()

Class Provider(models.Model):
    ...

我想查询课程列表,但同一提供商不能提供两门课程。也就是说,只选择每个提供者中最相关的课程。 我这样做了,

Course.objects.filter(xxxx).distinct('provider')

这工作正常,但如果我想将 order_by 添加到这个查询中,比如

Course.objects.filter(xxxx).distinct('provider').order_by('rating')

发生错误。在 django 文档中,order_by() 中的字段必须以 distinct() 中的字段开头,顺序相同。

然而,我仍然找不到任何其他解决方法来做同样的事情。

有什么想法吗? 非常感谢。

【问题讨论】:

  • 你试过.order_by('rating', 'provider').distinct('provider')吗?
  • 我做到了,但这行不通。

标签: django postgresql


【解决方案1】:

您需要从“反向”的角度看待这个问题,并使用“注释”。以下是在视图中获得所需输出的方法:

#views.py
from xxx.models import Course, Provider
from django.db.models import Min

from django.shortcuts import render

def home(request):
    t = Provider.objects.annotate(best_rating = Min('course__rating')).filter('xxx').order_by('best_rating')

    return render(request, 'home.html', {
                                        't':t,
    })

那么这里发生了什么:

首先,“annotate”是 Django 执行通常称为“group by”语句的方式。不幸的是,它们根据调用的对象提供不同的结果。因此,在我们的案例中,我们必须关注“每个提供者的最佳课程”,而不是“评价最高的课程及其提供者”。

在我们的.annotate(best_rating = Min('course__rating')) 语句中,内部子句返回最佳课程(假设 1.0 Rating 是最好的,如果相反,则使用 Max())。返回值分配给名为 best_rating 的虚拟模型属性,您可以像任何其他模型成员一样在模板中调用该属性。它只是不会存储在数据库中。

字符串'course__rating' 是一个特殊的字符串。它告诉 Django ORM 我们想要找到最小值的字段。该字符串的格式为"<MODEL>__<FIELD>"。尽管提供者不知道他与课程的关系,但 Django 会为我们执行此反向查找。所以这个字符串基本上是说:“给我所有与提供者有关系的课程的评分字段”

annotate 像许多其他 querySet 方法一样返回一个 QuerySet,因此您可以根据需要过滤和订购新集合。

当然,您也可以在注释之前进行过滤,但请注意,当您进行诸如查找 min() 或 max() 值之类的聚合时,这可能会影响您的最终结果。

基本模板示例:

     #home.html
     {% for e in t %}
     {{ e.name }} {{ e.best_rating }}<br>
     {% endfor %}

看看Django Documentation for further information about annotations and reverse lookups。

01.04.2014 更新: 如果你想让你的查询由课程列表驱动,你可以使用以下语句使用 F() 语句

from xxx.models import Course, Provider
from django.db.models import Min, F
from django.shortcuts import render

def home(request):
    t = Course.objects.annotate(best_rating = Min('provider__course__rating')).filter(rating = F('best_rating'))

    return render(request, 'home.html', {
                                        't':t,
    })

此查询将按提供者和课程评分进行注释。但仅此一项就会返回所有提供者,其中包含一个课程评分最低的附加列。所以我们需要根据这个新值过滤每一行。为此,我们需要一个 F() 表达式,它匹配每一行的值。

【讨论】:

  • 这是有道理的,但是通过这种方式,我将得到一个提供者列表而不是课程列表。所以基本上,课程的信息都丢失了,换句话说,提供者无法访问,对吧?
  • 我明白你需要什么。请看看我更新的答案。
  • 您的解决方案非常接近.. 但是通过“.filter(rating = F('best_rating'))”,当一个提供者有多个课程时,每个提供者仍然可以在结果中包含多个课程有相同的评分。
  • 那么我们需要一个您想要选择的规则。如果您对此一无所知,只需在查询中添加:[0],您将获得第一门最佳课程。但解决方案首先需要一些业务需求。
【解决方案2】:

对于您的问题,实际上有一个更简单的解决方案。只需按照 Django 告诉你的去做:

Course.objects.filter(xxxx).distinct('provider').order_by('provider', 'rating')

我不知道幕后到底发生了什么,但这对我有用。只需将 distinct 字段添加为 first 顺序参数。之后,您可以添加自定义排序。相关/外键排序除外。例如。这不起作用:

Course.objects.filter(xxxx).distinct('provider').order_by('provider', 'some_model__created')

第二部分不是您所要求的,但这是一个典型的错误 - 请记住这一点。此外,在您的模型中使用“class Meta”排序时,这将不起作用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-10
    • 2019-09-19
    • 1970-01-01
    • 2021-09-12
    • 1970-01-01
    • 1970-01-01
    • 2016-07-01
    • 1970-01-01
    相关资源
    最近更新 更多