Django JSONField中的值的聚合和

sh7euo9m  于 2023-05-08  发布在  Go
关注(0)|答案(1)|浏览(187)

假设我有一个简单的模型设置**JSONField**

from django.db import models

import random

def simple_json_callable():
    return {"amount": random.randint(1, 100)}

def nested_json_callable():
    data = {
        "l1": {
            "l2": {
                "l3": {
                    "amount": random.randint(1, 100)
                }
            }
        }
    }
    return data

class Foo(models.Model):
    simple_json = models.JSONField(default=simple_json_callable)
    nested_json = models.JSONField(default=nested_json_callable)

我想从simple_jsonnested_json字段中获得***amount***key的和。
我尝试了以下查询

案例一:注解然后聚合

result = Foo.objects.annotate(
    simple_json_amount=Cast('simple_json__amount', output_field=models.IntegerField()),
    nested_json_amount=Cast('nested_json__l1__l2__l3__amount', output_field=models.IntegerField()),
).aggregate(
    simple_json_total=models.Sum('simple_json_amount'),
    nested_json_total=models.Sum('nested_json_amount'),
)

案例二:聚合物

result = Foo.objects.aggregate(
    simple_json_total=models.Sum(Cast('simple_json__amount', output_field=models.IntegerField())),
    nested_json_total=models.Sum(Cast('nested_json__l1__l2__l3__amount', output_field=models.IntegerField())),

)

在这两种情况下,我都得到了错误

django.db.utils.DataError: cannot cast jsonb object to type integer

提问

在Django中聚合JSONField值的和的正确方法是什么?

版本号

  • Django 3.1
  • Python 3.9.X
hc8w905p

hc8w905p1#

Django==3.2.X及以上版本

您可以使用 “双下划线” 来跨越关系,并可以获得聚合结果(如OP中所述)

from django.db import models
from django.db.models.functions import Cast

result = Foo.objects.aggregate(
    simple_json_total=models.Sum(
        Cast("simple_json__amount", output_field=models.IntegerField()),
    ),
    nested_json_total=models.Sum(
        Cast("nested_json__l1__l2__l3__amount", output_field=models.IntegerField()),
    ),
)

# Result
# {'simple_json_total': 92, 'nested_json_total': 39}

Django==3.1.X及旧版本

您可以使用**KeyTextTransform(...)**来提取键并聚合值。

from django.db import models
from django.db.models.functions import Cast
from django.db.models.fields.json import KeyTextTransform as KT

simple_kt_expr = KT('amount', 'simple_json')
nested_kt_expr = KT("amount", KT("l3", KT("l2", KT("l1", "nested_json"))))
result = Foo.objects.aggregate(
    simple_json_total=models.Sum(Cast(simple_kt_expr, output_field=models.IntegerField())),
    nested_json_total=models.Sum(Cast(nested_kt_expr, output_field=models.IntegerField())),

)

# Result
# {'simple_json_total': 92, 'nested_json_total': 39}

如果你正在使用***PostgreSQL***,并且不想使用 KeyTextTransform的嵌套用法“,你可以创建一个自定义Django DB函数,等价于***jsonb_extract_path_text(...)***

from django.db.models import Aggregate

class JSONBExtractPathText(Aggregate):
    function = 'jsonb_extract_path_text'
    template = None

    def get_template(self):
        # https://stackoverflow.com/a/38985104/8283848
        paths = str(self.extra['path'])[1:-1]
        return f"%(function)s(%(jsonb_field)s, {paths})"

    def as_sql(self, *args, **kwargs):
        kwargs["template"] = self.get_template()
        return super().as_sql(*args, **kwargs)

它可以被用作

from django.db import models
from django.db.models.functions import Cast

simple_expr = JSONBExtractPathText(path=["amount"], jsonb_field="simple_json")
nested_expr = JSONBExtractPathText(path=["l1", "l2", "l3", "amount"], jsonb_field="nested_json")
result = Foo.objects.aggregate(
    simple_json_total=models.Sum(Cast(simple_expr, output_field=models.IntegerField())),
    nested_json_total=models.Sum(Cast(nested_expr, output_field=models.IntegerField())),

)

# Result
# {'simple_json_total': 92, 'nested_json_total': 39}

引用

  1. Aggregate Django JSONField
  2. Django门票-#26511#33966
  3. Django PR
  4. String join without quotes

相关问题