如何聚合(最小/最大等)Django JSONField数据?

alT*_*Tus 26 django orm json

我正在使用django 1.9及其内置的JSONField和postgres 9.4.在我的模型的JSONFieldjson字段中,我使用一些值存储对象,包括数字.我需要聚合它们来找到最小/最大值.像这样的东西:

Model.objects.aggregate(min=Min('attrs__my_key'))
Run Code Online (Sandbox Code Playgroud)

提取特定密钥也很有用:

Model.objects.values_list('attrs__my_key', flat=True)
Run Code Online (Sandbox Code Playgroud)

上述查询失败attrs:"无法将关键字'my_key'解析为字段.不允许加入'attrs'."

有可能吗?

注意:1)我知道如何进行简单的postgres查询来完成这项工作,所以我专门搜索ORM解决方案,以便能够过滤等.2)我想这可以用(相对)新的查询表达式/查找api完成但是我还没有研究过它.

din*_*ltz 33

从django 1.11(尚未出现,所以这可能会改变)你可以使用django.contrib.postgres.fields.jsonb.KeyTextTransform而不是RawSQL.

在Django 1.10,你必须复制/粘贴KeyTransform到你自己KeyTextTransform和更换->与运营商->>,并#>#>>因此它返回文本,而不是JSON对象.

Model.objects.annotate(
    val=KeyTextTransform('json_field_key', 'blah__json_field'))
).aggregate(min=Min('val')
Run Code Online (Sandbox Code Playgroud)

您甚至可以KeyTextTransformSearchVectors中包含s以进行全文搜索

Model.objects.annotate(
    search=SearchVector(
        KeyTextTransform('jsonb_text_field_key', 'json_field'))
    )
).filter(search='stuff I am searching for')
Run Code Online (Sandbox Code Playgroud)

请记住,您也可以在jsonb字段中编制索引,因此您应该根据您的特定工作负载来考虑这一点.

  • 谢谢你.我花了一段时间来解码如何使用它.在我的例子中,json数据存储在模型字段'jdata'中(相当于问题中的'attrs'),json键是'createdDate',它是顶级的.min_result = Model.objects.annotate(val = KeyTextTransform('createdDate','jdata')).aggregate(min = Min('val')) (6认同)
  • 请注意:从 django 3.1 开始 ```KeyTextTransform``` 可以通过 ```from django.db.models.fields.json import KeyTextTransform``` 导入 (3认同)

alT*_*Tus 18

对于那些感兴趣的人,我找到了解决方案(或至少解决方法).

from django.db.models.expressions import RawSQL

Model.objects.annotate(
    val=RawSQL("((attrs->>%s)::numeric)", (json_field_key,))
).aggregate(min=Min('val')
Run Code Online (Sandbox Code Playgroud)

请注意,attrs->>%s表达式将attrs->>'width'在处理后变为smth (我的意思是单引号).因此,如果您对此名称进行硬编码,则应记住插入它们,否则您将收到错误消息.

///有点offtopic ///

还有一个棘手的问题与django本身无关,但需要以某种方式处理.和attrsjson字段一样,它的键和值没有限制,你可以(取决于你的应用程序逻辑)获得一些非数字值,例如widthkey.在这种情况下,您将从DataError执行上述查询后获得postgres.同时会忽略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;
Run Code Online (Sandbox Code Playgroud)

然后使用它将文本转换为数字:

Model.objects.annotate(
    val=RawSQL("safe_cast_to_numeric(attrs->>%s)", (json_field_key,))
).aggregate(min=Min('val')
Run Code Online (Sandbox Code Playgroud)

因此,我们为像json这样的动态事物得到了非常可靠的解决方案.


小智 8

我知道这有点晚了(几个月),但是我在尝试执行此操作时遇到了这个帖子。设法做到这一点:

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)
Run Code Online (Sandbox Code Playgroud)

其中“数据”是jsonb属性,“数字_val”是我通过注释创建的变量的名称。

希望这对某人有帮助!


Sam*_*ums 5

可以使用 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))
Run Code Online (Sandbox Code Playgroud)

这比使用 好得多,RawQuery因为如果您执行更复杂的查询,它不会中断,其中 Django 使用别名并且存在字段名称冲突。ORM 有很多事情可以用手写的实现来咬你。