使用 JSONField 的属性对 Django 查询集进行排序,前提是该属性并非随处可见

Ordering Django queryset using attributes of a JSONField, provided the attribute is not present everywhere

这是一个非常复杂的问题,所以让我解释一下。我有一个名为 Person 的模型,它将大部分数据存储在 JSONField 中。

class Person(models.Model):
    data = JSONField()

现在,数据字段通常采用以下格式:

{"name" : <String>, "age" : <int>}

现在,我想要做的是创建一个 Person 的查询集,它使用 data 字段中的 age 属性对对象进行降序排序。这是使用以下代码解决的:

from django.db.models.expressions import RawSQL
from .models import Person

qs = Person.objects.annotate(age=RawSQL("(data->>'age')::int", [])).order_by('-age')

这很棒,而且效果很好。但是,在测试期间,我将一个 Person 对象的 data 属性更改为如下内容:

{"name" : <String>, "profession" : <String>} 

也就是说,此对象在其 data 字段中没有 age 属性。现在,当我 运行 上面的查询时,它仍然可以正常工作,但是这个对象(没有 age 属性的对象)位于最顶部。这是由于以下两个原因之一:

我真正想做的是将所有在 data 字段中没有 age 属性的对象发送到查询集的最后。

我通过创建 2 个查询集(一个年龄不为空,一个年龄为空)尝试了联合方法,并使用 | 运算符加入它们。这没有用,因为顺序搞砸了。我也尝试了我在另一个问题中发现的这种奇怪的方法(也没有用):

qs = Person.objects.annotate(age=RawSQL("(data->>'age')::int", [])).extra(select={'is_top': "age__isnull=True"})
qs = qs.extra(order_by('-is_top')

Link to the weird solution that did not work

不管怎样,有没有不涉及列表、itertools 和链的方法?因为我听说他们有时会很慢。

谢谢!

注意:请不要回答有关为这些查询规范化数据库而不是使用 JSONFields 的问题。我很清楚规范化的好处,但对于我的用例,它必须是 JSONField。

如果键查找失败,结果为 NULL,如 PostgreSQL documentation:

中指定

Note: There are parallel variants of these operators for both the json and jsonb types. The field/element/path extraction operators return the same type as their left-hand input (either json or jsonb), except for those specified as returning text, which coerce the value to text. The field/element/path extraction operators return NULL, rather than failing, if the JSON input does not have the right structure to match the request; for example if no such element exists.

可以做到这一点,你只需用.desc(nulls_last=True) [Django-doc]:

下单
from django.db.models import F, RawSQL
from .models import Person

qs = Person.objects.annotate(
    age=RawSQL("(data->>'age')::int", [])
).order_by(F('age')<b>.desc(nulls_last=True)</b>)

这将按以下顺序排列元素:

-- SQL query
ORDER BY <b>age IS NULL</b>, age DESC

因此,首先按 age IS NULL 排序,这将导致 TRUE,结果,它被排序在结果的 底部 table.