聚集JSONField的JSON键值Django PostgreSQL

qlfbtfca  于 2023-05-06  发布在  PostgreSQL
关注(0)|答案(2)|浏览(140)

我有一个简单的模型设置如下,

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.

Foo.objects.aggregate(total=models.Sum(Cast('cart__total_price', output_field=models.IntegerField())))

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

2.

Foo.objects.aggregate(total=models.Sum('cart__total_price', output_field=models.IntegerField()))

# Error
# 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的顶级JSON键的总和的正确/最佳方法是什么?

版本号

  • Python 3.8
  • Django 3.1
7rtdyuoh

7rtdyuoh1#

您可以使用KeyTextTransform和聚合函数来实现。

from django.db.models import Sum, IntegerField
    from django.db.models.fields.json import KeyTextTransform
    from django.db.models.functions import Cast

    # Sum of total_price from all Foo instances
    total_price_sum = Foo.objects.annotate(
        total_price_int=KeyTextTransform('total_price', 'cart')
    ).aggregate(
        total=Sum(Cast('total_price_int', output_field=IntegerField()))
    )

    print(total_price_sum['total'])

使用KeyTextTransform从**'cart'JSON字段中提取'total_price'键的值。
然后,使用
Sum函数和Cast**函数聚合提取值的总和,将值转换为整数。

relj7zay

relj7zay2#

我为***jsonb_extract_path_text(...)***创建了一个自定义DB函数

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.models.functions import Cast
from django.db.models import Sum, IntegerField
from .models import Foo

result = Foo.objects.aggregate(
    total_price=Sum(
        Cast(
            JSONBExtractPathText(
                path=["nested", "price"],
                jsonb_field="cart"),
            output_field=IntegerField(),
        )
    )
)
print(result)
# {'total_price': 100}

相关问题