一尘不染

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

json

我正在使用内置的Django 1.9 JSONField和Postgres
9.4。在模型的attrsjson字段中,我存储带有一些值(包括数字)的对象。我需要汇总它们以找到最小/最大值。像这样:

Model.objects.aggregate(min=Min('attrs__my_key'))

另外,提取特定的密钥将很有用:

Model.objects.values_list('attrs__my_key', flat=True)

上面的查询失败了

FieldError:“无法将关键字’my_key’解析为字段。不允许加入’attrs’。”

有可能吗?

笔记:

  1. 我知道如何进行简单的Postgres查询来完成这项工作,但是我正在专门搜索具有过滤功能的ORM解决方案。
  2. 我想可以(相对)使用新的查询表达式/查找API来完成此操作,但我尚未对此进行研究。

阅读 337

收藏
2020-07-27

共1个答案

一尘不染

对于那些有兴趣的人,我已经找到了解决方案(或至少解决方法)。

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'处理后一样(我是说单引号)。因此,如果您对该名称进行硬编码,则应记住将其插入,否则会出错。

///有点离题///

还有一个与Django本身无关的棘手问题,但需要以某种方式解决。与attrsjson字段一样,它的键和值也没有限制,您可以(取决于应用程序逻辑)在width键中获取一些非数字值。在这种情况下,您将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;

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

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

因此,对于诸如json这样的动态事物,我们得到了相当可靠的解决方案。

2020-07-27