Обратный поиск внешних отношений, влияющий на вывод кверисета в Django
Я выполняю сложный запрос в Django для базы данных MySql, и результат не соответствует ожидаемому.
Я использую функцию Django TruncMonth() для аннотирования результатов по месяцам, что работает, как и ожидалось. После аннотирования набора запросов по месяцам я добавляю подсчет количества заказов за месяц, что опять же работает как ожидалось.
Однако когда я пытаюсь получить общий доход за месяц, это каким-то образом влияет на подсчет общего количества заказов за месяц (см. ниже).
models.py
class Order(models.Model):
OrderId = models.BigAutoField(primary_key= True)
OrderDate = models.DateTimeField(default= timezone.now, verbose_name= 'Order Date')
...
class OrderItem(models.Model):
OrderItemId = models.BigAutoField(primary_key= True)
ArtworkItemId = models.ForeignKey(ArtworkItem, on_delete= models.SET_NULL, null= True, verbose_name= 'Artwork Item')
OrderId = models.ForeignKey(Order, on_delete= models.CASCADE, verbose_name= 'Order')
ItemQuantity = models.IntegerField(verbose_name= 'Quantity')
Полный запрос:
Order.objects.annotate(year= ExtractYear('OrderDate'),
month= TruncMonth('OrderDate')).values(
'year', 'month').filter(
year= timezone.now().year, OrderPaid= True).annotate(
orders= Count('OrderId'), revenue= Sum(F('orderitem__ArtworkItemId__ArtworkPrice') * F('orderitem__ItemQuantity')))
# <QuerySet [{'year': 2022, 'month': 'Jul', 'orders': 1, 'revenue': Decimal('180')}, {'year': 2022, 'month': 'Aug', 'orders': 3, 'revenue': Decimal('525')}]>
Вот полный SQL-запрос, сгенерированный Django, для наглядности:
SELECT django_datetime_extract('year', "art_order"."OrderDate", 'UTC', 'UTC') AS "year",
django_datetime_trunc('month', "art_order"."OrderDate", 'UTC', 'UTC') AS "month",
COUNT("art_order"."OrderId") AS "orders",
CAST(SUM(CAST(("art_artworkitem"."ArtworkPrice" * "art_orderitem"."ItemQuantity") AS NUMERIC)) AS NUMERIC) AS "revenue"
FROM "art_order"
LEFT OUTER JOIN "art_orderitem"
ON ("art_order"."OrderId" = "art_orderitem"."OrderId_id")
LEFT OUTER JOIN "art_artworkitem"
ON ("art_orderitem"."ArtworkItemId_id" = "art_artworkitem"."ArtworkItemId")
WHERE ("art_order"."OrderPaid" AND "art_order"."OrderDate" BETWEEN '''2022-01-01 00:00:00''' AND '''2022-12-31 23:59:59.999999''')
GROUP BY django_datetime_extract('year', "art_order"."OrderDate", 'UTC', 'UTC'),
django_datetime_trunc('month', "art_order"."OrderDate", 'UTC', 'UTC')
Однако когда я удаляю аргумент revenue:
Order.objects.annotate(year= ExtractYear('OrderDate'),
month= TruncMonth('OrderDate')).values(
'year', 'month').filter(
year= timezone.now().year, OrderPaid= True).annotate(
orders= Count('OrderId'))
# <QuerySet [{'year': 2022, 'month': 'Jul', 'orders': 1}, {'year': 2022, 'month': 'Aug', 'orders': 2}]>
пара ключ-значение orders
имеет правильные значения: 1 для июля и 2 для августа.
Похоже, что поскольку внешний ключ был просмотрен, он подсчитывает количество OrderItems, а не Orders. Я не уверен, что здесь происходит, и буду благодарен за совет.
Вы можете просто сделать следующее:
qs_order = Order.objects.filter(
OrderDate__year=timezone.now().year
OrderPaid=True
).annotate(
revenue=F('orderitem__ArtworkItemId__ArtworkPrice') * F('orderitem__ItemQuantity')
)
count = qs_order.count()
total_revenue = qs_order.aggregate(tot=Sum(F('revenue'))['tot']
Я оставлю свой ответ здесь для всех желающих в будущем, если он окажется полезным.
Мне удалось решить эту проблему, установив distinct = True
на Count
функцию.
Мое объяснение, полученное в результате тестирования:
Похоже, что при вызове annotate()
django затем форматирует SQL с точки зрения обратного отношения (если таковое существует). Так, например, в данном случае все вызовы полей были оценены с позиции обратного отношения, orderitem
(несмотря на то, что исходный вызов исходил из Order
).
Чтобы было еще понятнее, каждый orderitem
, по которому выполнялся цикл, обращался к атрибуту OrderId__OrderId
, который каждый раз добавлял 1. Это вместо того, чтобы подойти к этому с другой стороны и посчитать один OrderId
для двух orderitems
. Таким образом, если в одном заказе два товара, то изначально это воспринимается как два заказа. Установка distinct = True
не добавляет дублирующий OrderId к подсчету.