Django - Агрегат из агрегата
Я работаю с DRF и испытываю проблемы с определением набора queryset для использования в классе представления. Предположим, у меня есть три модели, например:
class ExchangeRate(...):
date = models.DateField(...)
rate = models.DecimalField(...)
from_currency = models.CharField(...)
to_currency = models.CharField(...)
class Transaction(...):
amount = models.DecimalField(...)
currency = models.CharField(...)
group = models.ForeignKey("TransactionGroup", ...)
class TransactionGroup(...):
...
Я хочу создать набор запросов на TransactionGroup
уровне со следующим:
-
- для каждой
Transaction
в группе транзакций добавьте аннотированное полеconverted_amount
, которое умножаетamount
наrate
в томExchangeRate
случае, когдаcurrency
совпадает сto_currency
соответственно
- для каждой
-
- затем суммируйте
converted_amount
для каждогоTransaction
и установите это наTransactionGroup
уровне как аннотированное полеconverted_amount_sum
- затем суммируйте
Пример json-ответа для TransactionGroup
с использованием этого желаемого набора запросов:
[
{
"id": 1,
"converted_amount_sum": 5000,
"transactions": [
{
"id": 1,
"amount": 1000,
"converted_amount": 500,
"currency": "USD",
},
{
"id": 2,
"amount": 5000,
"converted_amount": 4500,
"currency": "EUR",
},
},
...
]
Моя попытка построить кверисет (есть ли способ построить его на TransactionGroup
уровне?):
from django.db.models import F
annotated_transactions = Transaction.objects.annotate(
converted_amount = F("amount") * exchange_rate.rate # <-- simplifying here
).values(
"transaction_group"
).annotate(
amount=Sum("converted_amount"),
)
Я могу заставить аннотации правильно работать на модели Transaction
, но при попытке их повторного суммирования на уровне TransactionGroup
возникает ошибка:
FieldError: Cannot compute Sum('converted_amount'), `converted_amount` is an aggregate
Для дополнительного контекста - я хочу иметь возможность сортировать и фильтровать TransactionGroups
по convreted_amount_sum
без необходимости выполнять дополнительные поиски/операции в БД.
Вы можете работать с выражением Subquery
[Django-doc]:
from datetime import date
from django.db.models import F, OuterRef, Subquery
FILTER_DATE = date.today()
annotated_transactions = (
Transaction.objects.values('transaction_group')
.annotate(
amount=Sum(
F('converted_amount')
* Subquery(
ExchangeRate.objects.filter(
from_currency=OuterRef('currency'),
to_currency='USD',
date=FILTER_DATE,
).values('rate')[:1]
)
),
)
.order_by('transaction_group')
)
Но моделирование, вероятно, только усложняет ситуацию. Вы могли бы сделать модель Currency
и работать со связями, чтобы сделать выражение намного проще:
class Currency(models.Model):
code = models.CharField(max_length=4, primary_key=True)
class ExchangeRate(models.Model):
date = models.DateField()
rate = models.DecimalField()
from_currency = models.ForeignKey(
Currency,
db_column='from_currency',
related_name='exchange_from',
on_delete=models.PROTECT,
)
to_currency = models.ForeignKey(
Currency,
db_column='to_currency',
related_name='exchange_to',
on_delete=models.PROTECT,
)
class Transaction(models.Model):
amount = models.DecimalField()
currency = models.ForeignKey(
Currency,
db_column='currency',
related_name='transactions',
on_delete=models.PROTECT,
)
group = models.ForeignKey('TransactionGroup', on_delete=models.PROTECT)
Тогда мы можем работать с:
from datetime import date
from django.db.models import F, Sum
FILTER_DATE = date.today()
TransactionGroup.objects.filter(
transaction__currency__exchange_from__exchange_to_id='USD',
transaction__currency__exchange_from__date=FILTER_DATE,
).annotate(
total_amount=Sum(
F('transaction__amount') * F('transaction__currency__exchange_from__rate')
)
)
Для этого потребуется добавить "фиктивный обменный курс", желательно для каждой валюты, такой, что USD
к USD
на любую дату будет 1.0
. Хотя можно обойтись и без такого фиктивного курса, это усложнит задачу.
Таким образом, мы соединяем баланс транзакции с курсами валют на определенную дату, а затем суммируем преобразованные значения.