Как вычислить кумулятивную сумму поля count в Django
У меня есть модель, которая регистрирует некое событие и дату, в которую оно происходит. Мне нужно вычислить: 1) количество событий для каждой даты и 2) кумулятивное количество событий за определенное время.
Моя модель выглядит примерно так:
class Event(models.Model):
date = models.DateField()
...
Вычисление 1) довольно простое, но у меня возникают проблемы с вычислением кумулятивной суммы. Я пытался сделать что-то вроде этого:
query_set = Event.objects.values("date") \
.annotate(count=Count("date")) \
.annotate(cumcount=Window(Sum("count"), order_by="date"))
Но я получаю эту ошибку:
Cannot compute Sum('count'): 'count' is an aggregate
Редактируйте: В идеале, я хотел бы иметь набор запросов, эквивалентный этому SQL-запросу:
SELECT date,
COUNT(date) as count,
SUM(COUNT(date)) OVER(ORDER BY date) acc_count
FROM event_event
GROUP BY date
Удивительно часто можно встретить разработчиков, желающих преобразовать SQL-запрос в Django QuerySet.
В данном случае, поскольку ОП уже знает SQL, ОП лучше просто выполнить необработанный SQL запрос.
Есть разные способы сделать это, например выполнение пользовательского SQL напрямую.
from django.db import connection
def my_custom_sql(self):
with connection.cursor() as cursor:
cursor.execute("SELECT date, COUNT(date) as count, SUM(COUNT(date)) OVER(ORDER BY date) acc_count
FROM event_event
GROUP BY date")
Затем вызовите cursor.fetchone()
или cursor.fetchall()
, чтобы вернуть результирующие строки.
В некоторых случаях выполнение агрегата агрегата не допустимо в SQL, независимо от того, используете вы ORM или нет, например: MAX(SUM(...))
.
В вашем случае вы можете сделать это с помощью необработанного запроса (как уже упоминалось в других ответах и в вашем запросе).
Или используя ORM следующим образом:
subquery = (
Event.objects.filter(date=OuterRef("date")) # we need this for the join
.values("date") # this to create the group by
.annotate(subcount=Count("date")) # the aggregate function
)
Event.objects.values("date").annotate(count=Count("date")).annotate(
sumcount=Window(Sum(subquery.values("subcount")), order_by="date")
# above we can use the Sum with the subquery
# we can also replace it for any aggregation functions that we want
).values("date", "count", "cumcount")
В результате будет сгенерирован следующий SQL:
SELECT
"app_event"."date",
COUNT("app_event"."date") AS "count",
SUM((SELECT
COUNT(U0."date") AS "subcount"
FROM
"app_event" U0
WHERE
U0."date" = ("app_event"."date")
GROUP BY
U0."date"
)) OVER ( ORDER BY "app_event"."date")
AS "cumcount"
FROM
"app_event"
GROUP BY
"app_event"."date"