Django PostgreSQL中JSONField顶层键值的聚合求和最优方案
问题解决:Django JSONField顶层键值的总和计算
模型定义
我定义了如下简单模型:
import random import string from django.db import models def random_default(): random_str = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10)) return {"random": random_str, "total_price": random.randint(1, 100)} class Foo(models.Model): cart = models.JSONField(default=random_default)
需求与低效实现
我想获取所有Foo实例中total_price的总和。用原生Python可以实现,但效率很低:
sum(foo.cart["total_price"] for foo in Foo.objects.all())
失败的聚合尝试
我试了两种Django聚合查询,都没能正常运行:
1. 尝试1
Foo.objects.aggregate(total=models.Sum(Cast('cart__total_price', output_field=models.IntegerField())))
错误信息:
django.db.utils.DataError: cannot cast jsonb object to type integer
2. 尝试2
Foo.objects.aggregate(total=models.Sum('cart__total_price', output_field=models.IntegerField()))
错误信息:
django.db.utils.ProgrammingError: function sum(jsonb) does not exist LINE 1: SELECT SUM("core_foo"."cart") AS "total" FROM "core_foo" ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts.
核心问题
获取JSONField顶层键值总和的正确/最优方法是什么?
版本信息
- Python 3.8
- Django 3.1.X
解决方案
针对Django 3.1+和PostgreSQL(JSONField默认使用jsonb存储),需要先正确提取JSON字段中的total_price值,再完成类型转换与聚合:
方法1:使用KeyTextTransform + Cast
from django.db.models import IntegerField, Sum, Cast from django.db.models.fields.json import KeyTextTransform total = Foo.objects.aggregate( total=Sum( Cast( KeyTextTransform('total_price', 'cart'), output_field=IntegerField() ) ) )['total']
KeyTextTransform负责从JSON字段中提取指定键的字符串值,再通过Cast转换为整数类型,最后用Sum完成聚合计算。
方法2:使用原生JSON操作符
直接借助PostgreSQL的JSON操作符简化逻辑:
from django.db.models import Sum, IntegerField from django.db.models.functions import Cast total = Foo.objects.aggregate( total=Sum( Cast('cart->>\'total_price\'', output_field=IntegerField()) ) )['total']
cart->>'total_price'是PostgreSQL的原生语法,直接提取JSON字段中total_price对应的文本值,转换为整数后求和。
两种方法都在数据库层面完成计算,无需将所有实例加载到内存,效率远高于原生Python遍历求和。
内容的提问来源于stack exchange,提问作者JPG
相关产品推荐
相关产品推荐

