我正在使用Django 1.9及其内置的JSONField
和Postgres 9.4。
在我的模型的attrs
json字段中,我使用一些值(包括数字)存储对象。我需要聚合它们来找到最小/最大值。
像这样:
Model.objects.aggregate(min=Min('attrs__my_key'))
此外,提取特定密钥会很有用:
Model.objects.values_list('attrs__my_key', flat=True)
上述查询失败,
FieldError:“无法将关键字'my_key'解析为字段。不允许加入'attrs'。”
有可能吗?
注意:
答案 0 :(得分:27)
从django 1.11(尚未发布,因此可能会发生变化),您可以使用django.contrib.postgres.fields.jsonb.KeyTextTransform
代替RawSQL
。
在django 1.10中,您必须将KeyTransform
复制/粘贴到您自己的KeyTextTransform
,并将->
运算符替换为->>
和#>
{{1}所以它返回文本而不是json对象。
#>>
您甚至可以在Model.objects.annotate(
val=KeyTextTransform('json_field_key', 'blah__json_field'))
).aggregate(min=Min('val')
中添加KeyTextTransform
进行全文搜索
SearchVector
请记住,您也可以在jsonb字段中编制索引,因此您应该根据您的特定工作量考虑这一点。
答案 1 :(得分:17)
对于那些感兴趣的人,我找到了解决方案(至少是解决方法)。
from django.db.models.expressions import RawSQL
Model.objects.annotate(
val=RawSQL("((attrs->>%s)::numeric)", (json_field_key,))
).aggregate(min=Min('val')
请注意,attrs->>%s
表达式在处理后会变为像attrs->>'width'
一样(我的意思是单引号)。因此,如果您对此名称进行硬编码,则应记住插入它们,否则您将收到错误。
///有点offtopic ///
还有一个棘手的问题与django本身无关,但需要以某种方式处理。由于attrs
是json字段,并且它的键和值没有限制,您可以(取决于您的应用程序逻辑)获取一些非数字值,例如width
键。在这种情况下,由于执行上述查询,您将从postgres获得DataError
。同时会忽略NULL值,所以没关系。如果你能抓住错误那么没问题,你很幸运。在我的情况下,我需要忽略错误的值,这里唯一的方法是编写自定义的postgres函数来抑制转换错误。
create or replace function safe_cast_to_numeric(text) returns numeric as $$
begin
return cast($1 as numeric);
exception
when invalid_text_representation then
return null;
end;
$$ language plpgsql immutable;
然后使用它将文本转换为数字:
Model.objects.annotate(
val=RawSQL("safe_cast_to_numeric(attrs->>%s)", (json_field_key,))
).aggregate(min=Min('val')
因此,我们得到了像json这样充满活力的解决方案。
答案 2 :(得分:5)
我知道这有点晚了(几个月),但我试图这样做时遇到了这个帖子。通过以下方式管理:
1)使用KeyTextTransform将jsonb值转换为文本
2)使用Cast将其转换为整数,以便SUM工作:
q = myModel.objects.filter(type=9) \
.annotate(numeric_val=Cast(KeyTextTransform(sum_field, 'data'), IntegerField())) \
.aggregate(Sum('numeric_val'))
print(q)
其中'data'是jsonb属性,'numeric_val'是我通过注释创建的变量的名称。
希望这有助于某人!
答案 3 :(得分:1)
可以使用Postgres函数执行此操作
https://www.postgresql.org/docs/9.5/functions-json.html
from django.db.models import Func, F, FloatField
from django.db.models.expressions import Value
from django.db.models.functions import Cast
text = Func(F(json_field), Value(json_key), function='jsonb_extract_path_text')
floatfield = Cast(text, FloatField())
Model.objects.aggregate(min=Min(floatfield))
这比使用RawQuery
更好,因为如果您执行更复杂的查询(在Django使用别名并且字段名冲突的地方),它不会中断。 ORM发生了很多事情,可能会给您手写实现带来麻烦。
答案 4 :(得分:0)
似乎没有本地方法可以做到。
我这样工作:
my_queryset = Product.objects.all() # Or .filter()...
max_val = max(o.my_json_field.get(my_attrib, '') for o in my_queryset)
这远非奇妙,因为它是在Python级别完成的(而不是在SQL级别)。
答案 5 :(得分:0)
自 Django 3.1 起,JSON 字段上的 KeyTextTransform
函数可以运行 for all database backends。它映射到 ->>
operator in Postgres。
它可用于在聚合之前在查询集结果上注释特定的 JSON 键。一个更清晰的例子如何利用它:
首先将存储的 JSON 的 my_key
的 attrs
属性注释到查询集(在本例中为整数字段。需要 Cast
以与所有数据库后端保持兼容):< /p>
from django.db.models import IntegerField
from django.db.models.fields.json import KeyTextTransform
qs = Model.objects.annotate(
my_key=Cast(
KeyTextTransform("my_key", "attrs"), models.IntegerField()
)
现在您可以根据自己的喜好进行聚合:
from django.db.models import Min, Max, Avg, IntegerField
from django.db.models.functions import Cast, Round
qs.aggregate(
min_value=Round(Min("my_key")),
max_value=Round(Max("my_key")),
avg_value=Cast(Round(Avg("my_key")), IntegerField()),
)
>>> {'min_value': 1, 'max_value' 10:, 'avg_value': 6}