Агрегация суммы по полю 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
поля записей данной модели в базе данных.
Это поле может быть пустым или иметь разные ключи для каждой записи в базе данных, например, я захватил участок базы данных, чтобы лучше объяснить, что я имею в виду:
Для этой цели я написал следующий фрагмент кода на чистом 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