来自 Django JSONField 的值的总和

问题描述 投票:0回答:1

考虑我有一个简单的模型设置

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)

我想从amount

simple_json
字段中获得
nested_json
键的总和。

我尝试了以下查询

案例一:先标注再聚合


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'),
)

案例 2:聚合

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.X
  • Python 3.9.X
python django django-orm django-jsonfield jsonfield
1个回答
0
投票

对于
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. 聚合 Django
    JSONField
  2. Django 门票 - #26511#33966
  3. Django 公关 - #16016
  4. 不带引号的字符串连接
© www.soinside.com 2019 - 2024. All rights reserved.