Агрегация суммы по полю JsonField с помощью Django Orm

У меня есть модель в моем проекте django со следующей структурой:

class Portfolio(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    entered_usdt = models.DecimalField(max_digits=22, decimal_places=2, default=0)
    detail = models.JSONField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

Я хотел бы иметь Sum агрегатную функцию над ненулевыми значениями ключей detail поля записей данной модели в базе данных.

Это поле может быть пустым или иметь разные ключи для каждой записи в базе данных, например, я захватил участок базы данных, чтобы лучше объяснить, что я имею в виду:

Some records screenshot

Для этой цели я написал следующий фрагмент кода на чистом Python, который сначала извлекает ключи для записей, чьи detail не пусты, а затем вычисляет сумму всех этих ключей во всех этих записях.

import json
from bestbuy.models import Portfolio
from django.db.models import Q

def calculate_sum_of_all_basket_coins():
    non_zero_portfolios_details = list(Portfolio.objects.filter(~Q(detail={})).values('detail'))

    detail_list = list()
    result = {}

    for detail in non_zero_portfolios_details:
        detail_list.append(json.loads(portfolio))


    for d in detail_list:
        for k in d.keys():
            if k in result: 
                result[k] += d.get(k, 0.0)
            else:
                result[k] = d.get(k, 0.0)
    return result

Я знаю, что этот метод совсем не хорош, потому что по мере увеличения количества записей в базе данных, этой функции требуется все больше и больше времени для выполнения этого вычисления. Я также прочитал следующие посты в блоге и вопросы, чтобы узнать что-то больше о django orm и возможности его поддержки этого вычисления и, например, я обнаружил с чем-то вроде функции KeyTextTransform, но все трюки в приведенных ниже ссылках предназначены для определенного ключа или ключей json-поля в базе данных, а не для каких-то неизвестных ключей и т.д.

Как я могу получить набор агрегированных значений этого поля, используя django orm?

Лучшей практикой здесь будет создание менеджера моделей, таким образом, его можно вызвать следующим образом:

Projects.objects.with_sums()

Их можно даже соединить в цепочку следующим образом:

Projects.objects.filter(…).with_sums()

Вам просто нужно аннотировать объект запроса, возвращаемый методом модели, суммой каждого поля - однако вам придется запрашивать сумму каждого поля отдельно.

Если вы хотите немного оптимизировать работу, вы можете позволить with_sums() принимать аргумент, в котором вы можете указать, для каких полей вы хотите получить суммы - я рекомендую передавать полный путь к json-полю (т.е.: "foo__bar__field").

References

https://docs.djangoproject.com/en/4.0/topics/db/managers/

https://docs.djangoproject.com/en/4.0/topics/db/aggregation/

https://docs.djangoproject.com/en/4.0/ref/models/querysets/#annotate

Вернуться на верх