考虑我有一个简单的模型设置
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'),
)
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.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}
JSONField